keyless-smoke.e2e.ts 4.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { cp, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'
  2. import { tmpdir } from 'node:os'
  3. import { zstdDecompress } from 'node:zlib'
  4. import { promisify } from 'node:util'
  5. import { join } from 'node:path'
  6. import { fileURLToPath } from 'node:url'
  7. import { describe, expect, it } from 'vitest'
  8. import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
  9. import { PREPARED_ENTRY_FILENAME, prepareDshPlugin } from '@deepseek-ai/dsh-repository-plugin'
  10. import type { SessionEvent } from '@deepseek-ai/dsh-session'
  11. const binScript = fileURLToPath(new URL('../../../packages/examples/cli-demo/src/bin.ts', import.meta.url))
  12. const configPath = fileURLToPath(new URL('./fixtures/cli.cordis.yml', import.meta.url))
  13. const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
  14. const decompress = promisify(zstdDecompress)
  15. describe('headless-agent keyless smoke', () => {
  16. it('boots the real Loader tree, runs a real bash tool round trip, and persists the turn', async () => {
  17. let persistedHeader: Record<string, unknown> | undefined
  18. const { stdout, stderr } = await runLoaderSmoke({
  19. label: 'headless-agent',
  20. tempDirPrefix: 'headless-agent-smoke-',
  21. binScript,
  22. configPath,
  23. binArgs: ['--config', configPath, '--output-format', 'stream-json', 'prove the tool path'],
  24. tsconfigPath,
  25. inspect: async (cwd) => {
  26. const files = await readdir(cwd, { recursive: true })
  27. const relativePath = files.find(file => file.endsWith('.jsonl.zstd'))
  28. if (relativePath === undefined) return
  29. const compressed = await readFile(join(cwd, relativePath))
  30. expect(compressed.subarray(0, 4).toString('hex')).toBe('28b52ffd')
  31. persistedHeader = JSON.parse((await decompress(compressed)).toString()) as Record<string, unknown>
  32. },
  33. })
  34. const lines = stdout.trimEnd().split('\n').map(line => JSON.parse(line) as Record<string, unknown>)
  35. const events = lines.slice(0, -1).map(line => line['event'] as SessionEvent)
  36. const result = lines.at(-1)
  37. expect(stderr).toBe('')
  38. expect(events.some(event => event.type === 'tool/call' && event.data.name === 'bash')).toBe(true)
  39. const catalogMessage = events.find(event => event.type === 'user/message'
  40. && event.data.source.kind === 'plugin'
  41. && event.data.source.plugin === 'dsh-tool-skill')
  42. const catalog = catalogMessage?.type === 'user/message'
  43. ? catalogMessage.data.content.filter(block => block.type === 'text').map(block => block.text).join('\n')
  44. : ''
  45. expect(catalog.split('\n').find(line => line.includes('repository-fixture'))).toMatchInlineSnapshot(
  46. `
  47. "- \`repository-fixture\`: Repository fixture skill."
  48. `,
  49. )
  50. const toolResult = events.find(event => event.type === 'tool/result')
  51. expect(JSON.stringify(toolResult)).toContain('CLI_TOOL_ROUND_TRIP')
  52. expect(result).toMatchObject({
  53. type: 'result',
  54. usage: { inputTokens: 18, outputTokens: 8, cacheReadTokens: 2, reasoningTokens: 1 },
  55. })
  56. expect(String(result?.['output'])).toContain('CLI_TOOL_ROUND_TRIP')
  57. expect(persistedHeader).toMatchObject({ type: 'session' })
  58. }, LOADER_SMOKE_TEST_TIMEOUT_MS)
  59. it('keeps the checked-in prepared wrapper identical to the generator output for its manifest', async () => {
  60. // The fixture claims "Generated by dsh-plugin-prepare"; this pin makes the
  61. // claim true — a wrapper-template change fails here until the fixture is
  62. // regenerated, so the assembled smoke can never exercise a stale shape.
  63. const fixture = fileURLToPath(new URL('./fixtures/repository-plugin/', import.meta.url))
  64. const root = await mkdtemp(join(tmpdir(), 'dsh-fixture-drift-'))
  65. try {
  66. const plugin = join(root, '.dsh-plugin')
  67. await mkdir(plugin, { recursive: true })
  68. await cp(join(fixture, 'dsh-plugin-assets/skills/0'), join(root, 'skills'), { recursive: true })
  69. await writeFile(join(plugin, 'package.json'), `${JSON.stringify({
  70. name: 'headless-repository-fixture',
  71. version: '0.0.0',
  72. dsh: { skills: ['../skills'] },
  73. }, undefined, 2)}\n`)
  74. await prepareDshPlugin(plugin)
  75. const generated = await readFile(join(plugin, PREPARED_ENTRY_FILENAME), 'utf8')
  76. const checkedIn = await readFile(join(fixture, PREPARED_ENTRY_FILENAME), 'utf8')
  77. expect(checkedIn).toBe(generated)
  78. } finally {
  79. await rm(root, { recursive: true, force: true })
  80. }
  81. })
  82. })