Files
deepseek-harness/packages/test-support/tool-testkit/src/index.ts
T
Yichen Jiang 207b30d299 Merge remote-tracking branch 'origin/master' into worktree/dynamic-tool-activation-pr-64796e
# Conflicts:
#	docs/config-catalog.md
#	docs/config-catalog.zh.md
#	docs/event-producer-consumer.md
#	docs/event-producer-consumer.zh.md
#	docs/persistence-catalog.i18n.yaml
#	docs/persistence-catalog.md
#	docs/persistence-catalog.zh.md
#	docs/persistence-schema.json
#	packages/core/agent-loop/src/agent.ts
#	packages/core/agent-loop/src/tool-calls.ts
#	packages/core/agent-loop/tests/tool-updates.spec.ts
#	packages/core/system-prompt/README.i18n.yaml
#	packages/core/system-prompt/README.md
#	packages/core/system-prompt/README.zh.md
#	packages/core/tool-executor-ptc/src/ptc.ts
#	packages/core/tool-executor-ptc/tests/ptc.spec.ts
#	packages/core/tools/README.i18n.yaml
#	packages/core/tools/README.md
#	packages/core/tools/README.zh.md
#	packages/core/tools/src/index.ts
#	packages/extensions/tool-cordis/src/api-catalog.ts
#	packages/plan/plan-mode/tests/plan-mode.spec.ts
#	snapshots/session/cordis-inspect-jsdoc/session.v4.jsonl
2026-10-21 23:32:15 +08:00

93 lines
4.0 KiB
TypeScript

/** Canonical tool-definition fixtures for repository tests. @module @deepseek-ai/dsh-tool-testkit */
import type { Context } from '@deepseek-ai/cordis'
import { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline'
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import { defineTool } from '@deepseek-ai/dsh-tools'
import type {
DefineToolOptions, InferArgs, ParameterSchemaSpec, ToolDefinition,
ToolExecutionInput, ToolExecutionResult, ToolRunContext,
} from '@deepseek-ai/dsh-tools'
// Expand interface fields so the fixture's content value is checked as JSON without changing runtime values.
type JsonFields<T> = T extends string | number | boolean | null | undefined ? T
: T extends (...args: never[]) => unknown ? T
: { [K in keyof T]: JsonFields<T[K]> }
type ContentValue = JsonFields<ContentBlock>
const CONTENT_VALUE_SCHEMA = { type: 'array', items: { type: 'json' } } as const
/** Options for a fixture whose canonical value is its rendered content array. */
export type ContentToolFixtureOptions<S extends ParameterSchemaSpec> = Omit<
DefineToolOptions<S, typeof CONTENT_VALUE_SCHEMA>,
'output' | 'execute'
> & {
/** Produce the fixture's content blocks as its canonical test value. */
execute(args: InferArgs<S>, exec: ToolRunContext): Promise<ContentBlock[]>
}
/**
* Define a test fixture that deliberately uses its content blocks as the
* canonical JSON value. Product tools must declare domain-owned DTOs instead.
* @param options - ordinary fixture fields plus a content-producing body.
* @returns a registry-ready tool with an explicit JSON-array output contract.
* @internal
*/
export function defineContentToolFixture<const S extends ParameterSchemaSpec>(
options: ContentToolFixtureOptions<S>,
): ToolDefinition {
// oxlint-disable-next-line typescript/unbound-method
const execute = options.execute
return defineTool({
...options,
output: {
schema: CONTENT_VALUE_SCHEMA,
render: (_args, value) => value as ContentValue[],
},
async execute(args, exec) {
const value: ContentValue[] = await execute(args, exec)
return value
},
})
}
/**
* Exercise invocation stages directly, preserving parsed and nested test inputs.
* Use executeModelToolFixture for a complete single-call batch.
* @param ctx - context owning the tool definitions and execution policy.
* @param input - parsed invocation, including nested identity when testing a composite.
* @returns the settled capability result.
* @internal
*/
export async function executeToolFixture(ctx: Context, input: ToolExecutionInput): Promise<ToolExecutionResult> {
const pipeline = new ToolExecutionPipeline(ctx)
const prepared = await pipeline.prepare(input)
const outcome = prepared.kind === 'dispatch' ? await pipeline.dispatch(prepared.exec) : prepared
return pipeline.complete(prepared.exec, outcome)
}
/**
* Run one raw call through the selected executor without root Session logging.
* @param ctx - context owning the selected executor.
* @param input - JSON arguments, optional Agent supplying scoped tools and tool context, and optional step admission.
* @returns the single settled result.
* @internal
*/
export async function executeModelToolFixture(
ctx: Context,
input: Pick<ToolExecutionInput, 'callId' | 'name' | 'arguments' | 'signal' | 'agent' | 'toolSet'>,
): Promise<ToolExecutionResult> {
const args = JSON.stringify(input.arguments)
// oxlint-disable-next-line typescript/no-unnecessary-condition -- JSON.stringify returns undefined for non-JSON fixture arguments
if (args === undefined) throw new TypeError('Model tool fixture arguments must encode as JSON')
const batch = await ctx.tools.execute({
calls: [{ type: 'tool-call', id: input.callId, name: input.name, arguments: args }],
signal: input.signal,
...input.agent === undefined ? {} : { agent: input.agent },
...input.toolSet === undefined ? {} : { toolSet: input.toolSet },
})
const outcome = batch.results[0]
if (outcome === undefined) throw new Error('Executor omitted the model tool fixture result')
return outcome.result
}