harness.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import { Context } from '@deepseek-ai/cordis'
  2. import LlmRuntime, { ToolCallId } from '@deepseek-ai/dsh-llm'
  3. import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
  4. import ToolRuntime from '@deepseek-ai/dsh-tools'
  5. import type { Agent, AgentOptions } from '@deepseek-ai/dsh-agent'
  6. import AgentLoop from '@deepseek-ai/dsh-agent-loop'
  7. import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
  8. import SubagentRuntime from '@deepseek-ai/dsh-subagent'
  9. import { Session, SessionId } from '@deepseek-ai/dsh-session'
  10. import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
  11. import * as mock from './scripted-provider.ts'
  12. import * as tool from '../src/index.ts'
  13. import SubagentModelSelectionConfig from '../src/model-selection-settings.ts'
  14. /** Shared non-aborted tool signal for package-local integration tests. */
  15. export const testToolSignal = new AbortController().signal
  16. /** Build the minimal parent Agent owned by the package-local scripted provider. */
  17. export function fakeAgent(id = 'parent-1'): Agent {
  18. const sessionId = SessionId(id)
  19. return { id: sessionId, options: {}, session: Session.create(sessionId) } as unknown as Agent
  20. }
  21. /** Mount the real tool and service stack around one scripted subagent provider. */
  22. const setupAgents = new WeakMap<Context, Agent>()
  23. const setupProviders = new WeakMap<Context, Awaited<ReturnType<typeof mock.mountScriptedProvider>>>()
  24. let setupAgentCounter = 0
  25. /** Test-only opt-in translated to the real Host setting and Session path. */
  26. type SetupConfig = tool.Config & {
  27. withModelSelection?: boolean
  28. parentAgentOptions?: AgentOptions
  29. }
  30. const TEST_ALLOWED_MODELS = [
  31. 'allowed-model', 'child-model', 'configured-model', 'current-model', 'fast-model',
  32. 'other-model', 'parent-model', 'selected-model', 'unlisted-model',
  33. ].flatMap(model => [
  34. { provider: 'alpha', model },
  35. { provider: 'current-provider', model },
  36. { provider: 'missing', model },
  37. ])
  38. export async function setup(toolConfig: SetupConfig, mockConfig: Partial<mock.Config> = {}): Promise<Context> {
  39. const ctx = new Context()
  40. const { withModelSelection, parentAgentOptions, ...config } = toolConfig
  41. if (withModelSelection === true) {
  42. await ctx.plugin(SubagentModelSelectionConfig, {
  43. enabled: true,
  44. allowedModels: TEST_ALLOWED_MODELS,
  45. })
  46. await mountAgentLoopTestDependencies(ctx)
  47. await ctx.plugin(AgentLoop, { agents: [] })
  48. await ctx.plugin(SubagentRuntime)
  49. const provider = await mock.mountScriptedProvider(ctx, { name: 'mock', ...mockConfig })
  50. setupProviders.set(ctx, provider)
  51. const handle = await ctx.agents.create({
  52. sessionId: SessionId(`model-selection-setup-${++setupAgentCounter}`),
  53. ...parentAgentOptions !== undefined ? { agentOptions: parentAgentOptions } : {},
  54. setup: async (agentCtx, agent) => {
  55. const fiber = agentCtx.inject(tool.inject, (runtimeCtx) => {
  56. tool.apply(runtimeCtx, { ...config, modelSelectionSettings: true }, agent.session)
  57. })
  58. await fiber.await()
  59. },
  60. })
  61. setupAgents.set(ctx, handle.agent)
  62. return ctx
  63. }
  64. await ctx.plugin(LlmRuntime)
  65. await ctx.plugin(SystemPrompt)
  66. await ctx.plugin(ToolRuntime)
  67. await ctx.plugin(SubagentRuntime)
  68. await ctx.plugin(SessionProjectionRegistry)
  69. const provider = await mock.mountScriptedProvider(ctx, { name: 'mock', ...mockConfig })
  70. setupProviders.set(ctx, provider)
  71. await ctx.plugin(tool, config)
  72. return ctx
  73. }
  74. /** Dispose the scripted provider mounted by {@link setup}. */
  75. export async function disposeSetupProvider(ctx: Context): Promise<void> {
  76. const provider = setupProviders.get(ctx)
  77. if (provider === undefined) throw new Error('context has no setup provider')
  78. setupProviders.delete(ctx)
  79. await provider.dispose()
  80. }
  81. /** Return the real Agent created for a settings-controlled setup. */
  82. export function modelSelectionSetupAgent(ctx: Context): Agent {
  83. const agent = setupAgents.get(ctx)
  84. if (agent === undefined) throw new Error('context has no model-selection setup Agent')
  85. return agent
  86. }
  87. let callCounter = 0
  88. /** Execute the registered subagent tool through the real ToolRuntime pipeline. */
  89. export function callSubagent(
  90. ctx: Context,
  91. args: unknown,
  92. over: { agent?: Agent | undefined; signal?: AbortSignal } = {},
  93. ) {
  94. // Distinguish "no override" (use a default agent) from an explicit
  95. // `{ agent: undefined }` (test the no-agent path). Under
  96. // exactOptionalPropertyTypes the key is omitted rather than set to undefined.
  97. const agent = 'agent' in over ? over.agent : setupAgents.get(ctx) ?? fakeAgent()
  98. return ctx.tools.execute({
  99. signal: testToolSignal,
  100. callId: ToolCallId(`call-${++callCounter}`),
  101. name: 'subagent',
  102. arguments: args,
  103. ...agent ? { agent } : {},
  104. ...over.signal ? { signal: over.signal } : {},
  105. })
  106. }
  107. /** Join text blocks from one rendered tool result. */
  108. export function text(result: { content: { type: string; text?: string }[] }): string {
  109. return result.content.filter(block => block.type === 'text').map(block => block.text).join('')
  110. }