driver.ts 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. #!/usr/bin/env node
  2. /**
  3. * Test driver: boot the tool-pwsh Loader composition, execute one real
  4. * foreground and one real background pwsh command through the tool registry,
  5. * and persist the observed model-visible output to `./pwsh-loader-report.json`
  6. * for the package spec's inspect step.
  7. */
  8. import { writeFile } from 'node:fs/promises'
  9. import { boot, resolveConfigPath } from '@deepseek-ai/dsh-app-boot'
  10. import { ToolCallId } from '@deepseek-ai/dsh-llm'
  11. const configPath = process.argv[2]
  12. if (configPath === undefined) throw new Error('tool-pwsh driver requires a config path')
  13. const ctx = await boot('tool-pwsh-loader-smoke', resolveConfigPath(configPath, undefined))
  14. try {
  15. const schema = ctx.tools.schemas().find(tool => tool.name === 'pwsh')
  16. if (schema === undefined) throw new Error('pwsh tool not registered by the composition')
  17. const prompt = (await ctx.systemPrompt.assemble()).sections.find(section => section.name === 'tool:pwsh')
  18. const foreground = await ctx.tools.execute({
  19. signal: new AbortController().signal,
  20. callId: ToolCallId('loader-fg'),
  21. name: 'pwsh',
  22. arguments: { command: 'Write-Output loader-ok', description: 'loader foreground' },
  23. })
  24. const foregroundText = foreground.content.filter(block => block.type === 'text').map(block => block.text).join('')
  25. const background = await ctx.tools.execute({
  26. signal: new AbortController().signal,
  27. callId: ToolCallId('loader-bg'),
  28. name: 'pwsh',
  29. arguments: {
  30. command: 'Start-Sleep -Milliseconds 200; Write-Output loader-bg-ok',
  31. description: 'loader background',
  32. run_in_background: true,
  33. },
  34. })
  35. const jobId = (background.value as { jobId: string }).jobId
  36. // The output delta and the terminal status can land in separate reads
  37. // (Windows flushes the child pipe at exit), so accumulate both.
  38. let backgroundText = ''
  39. const deadline = Date.now() + 10_000
  40. while (Date.now() < deadline) {
  41. const read = await ctx.tools.execute({
  42. signal: new AbortController().signal,
  43. callId: ToolCallId('loader-bg-read'),
  44. name: 'job_output',
  45. arguments: { job_id: jobId },
  46. })
  47. backgroundText += read.content.filter(block => block.type === 'text').map(block => block.text).join('')
  48. if (backgroundText.includes('loader-bg-ok') && backgroundText.includes('[status: completed')) break
  49. await new Promise(resolve => setTimeout(resolve, 50))
  50. }
  51. await writeFile('./pwsh-loader-report.json', JSON.stringify({
  52. schemaHasRunInBackground: Object.hasOwn(schema.parameters.properties as object, 'run_in_background'),
  53. promptHasMarkerSection: prompt?.text.includes('Non-zero exits are reported as `[exit code: N]` markers') === true,
  54. // Normalize PowerShell's platform line endings (CRLF on Windows, LF elsewhere).
  55. foregroundText: foregroundText.replace(/\r\n/g, '\n'),
  56. backgroundText: backgroundText.replace(/\r\n/g, '\n'),
  57. }))
  58. } finally {
  59. await ctx.fiber.dispose()
  60. }