Merge remote-tracking branch 'origin/master' into feat/send-unify

# Conflicts:
#	docs/persistence-catalog.md
#	examples/acp-agent/tests/snapshots/code-mode-workspace-context/session.jsonl
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/stdout.expected.jsonl
#	packages/context/time-context/tests/time-context.spec.ts
#	packages/cordis/tool-cordis/src/api-catalog.ts
This commit is contained in:
Turtle
2026-07-23 20:54:48 +08:00
430 changed files with 20734 additions and 5783 deletions
@@ -60,7 +60,7 @@ Independent of the parent request cache. The child's later history is append-onl
#### What the model sees
A structured run adds the structured-output instruction below. It also adds a child-scoped `structured_output` definition with exact description `Report your final structured result. Call this exactly once, when your answer is complete; the arguments must match this tool's parameter schema exactly.` and the requested schema. This runtime-only definition is outside the generated shipped [tool package map](../../../docs/tool-catalog.md#tool-package-map). Success returns `Structured output recorded.`; a later call becomes ``Error: structured output already recorded: the run is complete, so `<tool>` is not executed``.
A structured run adds the structured-output instruction below. It also adds a child-scoped `structured_output` definition with exact description `Report your final structured result. Call this exactly once, when your answer is complete; the arguments must match this tool's parameter schema exactly.` and the requested schema. This runtime-only definition is outside the generated shipped [tool package map](../../../docs/tool-catalog.md#tool-package-map). Its canonical acknowledgement is `{ recorded: true }`, rendered as `Structured output recorded.`; a later call becomes ``Error: structured output already recorded: the run is complete, so `<tool>` is not executed``.
##### Structured-output instruction
@@ -12,9 +12,9 @@
import type { Context } from 'cordis'
import type { ContinuationStop } from '@deepseek-ai/dsh-agent'
import type { ContentBlock, ToolSchema } from '@deepseek-ai/dsh-llm'
import type { ToolSchema } from '@deepseek-ai/dsh-llm'
import type { ToolExecution } from '@deepseek-ai/dsh-tools'
import { ToolArgsError, validateStructuredValue, type StructuredOutputSchema } from '@deepseek-ai/dsh-tools'
import { ToolArgsError, validateJsonSchemaValue, type ObjectJsonSchema } from '@deepseek-ai/dsh-tools'
/** The model-facing tool name a structured child must call to finish. */
export const STRUCTURED_OUTPUT_TOOL = 'structured_output'
@@ -44,10 +44,10 @@ export interface StructuredAttachment {
* its creation window. Child disposal removes every registration.
* @param childCtx - the child agent's scope context (`setup`'s argument).
* @param schema - the trusted, already-asserted schema subset to enforce (see
* `assertSupportedOutputSchema` in dsh-tools).
* `assertObjectJsonSchema` in dsh-tools).
* @returns the attachment handle (read `captured()` after the child settles).
*/
export function attachStructuredRuntime(childCtx: Context, schema: StructuredOutputSchema): StructuredAttachment {
export function attachStructuredRuntime(childCtx: Context, schema: ObjectJsonSchema): StructuredAttachment {
/**
* Validated values staged by the capture tool body, awaiting THEIR OWN
* authoritative `tools/result` notification. The execution object's identity
@@ -74,8 +74,17 @@ export function attachStructuredRuntime(childCtx: Context, schema: StructuredOut
childCtx.tools.register({
...schemaEntry,
execute(args: unknown, exec: ToolExecution): Promise<ContentBlock[]> {
const violations = validateStructuredValue(schema, args)
output: {
schema: {
type: 'object',
properties: { recorded: { type: 'boolean', const: true } },
required: ['recorded'],
additionalProperties: false,
},
render: () => [{ type: 'text', text: 'Structured output recorded.' }],
},
execute(args: unknown, exec: ToolExecution): Promise<{ recorded: true }> {
const violations = validateJsonSchemaValue(schema, args)
// ToolArgsError → isError result with INVALID_ARGS: the model retries
// within the same turn, exactly like a schema-validated defineTool call.
if (violations.length > 0) throw new ToolArgsError(violations)
@@ -83,7 +92,7 @@ export function attachStructuredRuntime(childCtx: Context, schema: StructuredOut
// waterfalls may still turn the success into an error. ToolRegistry has
// already frozen model-bound arguments at the actual input boundary.
staged.set(exec, { value: args })
return Promise.resolve([{ type: 'text', text: 'Structured output recorded.' }])
return Promise.resolve({ recorded: true })
},
})
@@ -10,8 +10,8 @@ import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant'
import * as AgentInvariant from '@deepseek-ai/dsh-agent/invariant'
import * as AgentLoopInvariant from '@deepseek-ai/dsh-agent-loop/invariant'
import SubagentService, { type SubagentStartRequest } from '@deepseek-ai/dsh-subagent'
import type { Config as ToolConfig, StructuredOutputSchema } from '@deepseek-ai/dsh-tools'
import { RUN_CODE_NAME } from '@deepseek-ai/dsh-tools'
import type { Config as ToolConfig, ObjectJsonSchema } from '@deepseek-ai/dsh-tools'
import { defineContentToolFixture, RUN_CODE_NAME } from '@deepseek-ai/dsh-tools'
import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
import { startInProcessRun } from '../src/index.ts'
import {
@@ -39,7 +39,7 @@ interface SetupOptions {
codeRun?: (request: CodeRunRequestLike) => Promise<{ logs: never[]; value?: unknown }>
}
const SCHEMA: StructuredOutputSchema = {
const SCHEMA: ObjectJsonSchema = {
type: 'object',
properties: { answer: { type: 'number' }, note: { type: 'string' } },
required: ['answer'],
@@ -97,10 +97,15 @@ describe('in-process structured output', () => {
const { ctx, parent } = await setup([
toolCallResponse('c1', STRUCTURED_OUTPUT_TOOL, { answer: 42, note: 'done' }),
])
let acknowledgement: unknown
ctx.on('tools/result', (exec, toolResult) => {
if (exec.name === STRUCTURED_OUTPUT_TOOL && !toolResult.isError) acknowledgement = toolResult.value
})
const run = await ctx.subagents.start('spawn', structuredRequest(parent))
const result = await run.result
expect(result.stopReason).toBe('completed')
expect(result.structured).toEqual({ answer: 42, note: 'done' })
expect(acknowledgement).toEqual({ recorded: true })
await run.dispose()
})
@@ -131,15 +136,15 @@ describe('in-process structured output', () => {
] as Script[number]
const { ctx, parent } = await setup([response])
let sideEffectRan = false
ctx.tools.register({
ctx.tools.register(defineContentToolFixture({
name: 'side_effect',
description: 'probe',
parameters: { type: 'object', properties: {} },
parameters: {},
execute(): Promise<ContentBlock[]> {
sideEffectRan = true
return Promise.resolve([{ type: 'text', text: 'ran' }])
},
})
}))
const run = await ctx.subagents.start('spawn', structuredRequest(parent))
const result = await run.result
expect(result.stopReason).toBe('completed')
@@ -159,15 +164,15 @@ describe('in-process structured output', () => {
] as Script[number]
const { ctx, parent } = await setup([response])
let sideEffectRan = false
ctx.tools.register({
ctx.tools.register(defineContentToolFixture({
name: 'side_effect',
description: 'probe',
parameters: { type: 'object', properties: {} },
parameters: {},
execute(): Promise<ContentBlock[]> {
sideEffectRan = true
return Promise.resolve([{ type: 'text', text: 'ran' }])
},
})
}))
const run = await ctx.subagents.start('spawn', structuredRequest(parent))
// Registered after the child and prepended: this listener returns allow
// after every downstream pre-execute decision. The service-owned guard
@@ -196,15 +201,15 @@ describe('in-process structured output', () => {
] as Script[number]
const { ctx, parent } = await setup([response])
let sideEffectRan = false
ctx.tools.register({
ctx.tools.register(defineContentToolFixture({
name: 'side_effect',
description: 'probe',
parameters: { type: 'object', properties: {} },
parameters: {},
execute(): Promise<ContentBlock[]> {
sideEffectRan = true
return Promise.resolve([{ type: 'text', text: 'ran' }])
},
})
}))
const run = await ctx.subagents.start('spawn', structuredRequest(parent))
const result = await run.result
// The call ran BEFORE captured was set: the deny gate only guards the
@@ -332,17 +337,17 @@ describe('in-process structured output', () => {
it('rejects a schema outside the subset loud, before any child exists', async () => {
const { ctx, parent } = await setup([])
await expect(ctx.subagents.start('spawn', structuredRequest(parent, {
outputSchema: { type: 'object', oneOf: [] } as unknown as StructuredOutputSchema,
}))).rejects.toThrow(/unsupported output schema/)
outputSchema: { type: 'object', oneOf: [] } as unknown as ObjectJsonSchema,
}))).rejects.toThrow(/unsupported JSON schema/)
expect(ctx.agents.get(SessionId('parent'))).toBeDefined()
})
it('a schema carrying non-JSON values fails as OutputSchemaError at the validation boundary', async () => {
it('a schema carrying non-JSON values fails as JsonSchemaError at the validation boundary', async () => {
const { ctx, parent } = await setup([])
// Semantic assertion runs before provider startup.
await expect(ctx.subagents.start('spawn', structuredRequest(parent, {
outputSchema: { type: 'object', default: () => {} } as unknown as StructuredOutputSchema,
}))).rejects.toThrow(/unsupported output schema.*annotation must be JSON data/)
outputSchema: { type: 'object', default: () => {} } as unknown as ObjectJsonSchema,
}))).rejects.toThrow(/unsupported JSON schema.*annotation must be lossless JSON data/)
})
it('a post-execute BLOCK on the capture call denies the capture: log and result agree on failure', async () => {
@@ -450,8 +455,10 @@ describe('in-process structured output', () => {
expect(result.structured).toEqual({ answer: 12 })
const request = adapter.requests[0]!
expect(toolNames(request)).toEqual([RUN_CODE_NAME])
expect(request.system).toContain('declare const tools:')
expect(request.system).toContain('structured_output(args:')
expect(request.system).toContain('interface ToolArgsMap')
expect(request.system).toContain('interface ToolOutputMap')
expect(request.system).toContain('recorded: true;')
expect(request.system).toContain('Promise<ToolOutputMap[K]>')
expect(request.system).toContain(STRUCTURED_OUTPUT_INSTRUCTION)
await run.dispose()
})
@@ -559,7 +566,7 @@ describe('in-process structured output', () => {
})
it('two concurrent structured children each see their OWN schema', async () => {
const otherSchema: StructuredOutputSchema = {
const otherSchema: ObjectJsonSchema = {
type: 'object',
properties: { verdict: { type: 'string', enum: ['real', 'bogus'] } },
required: ['verdict'],
@@ -601,12 +608,12 @@ describe('in-process structured output', () => {
])
// A global tool sorts lexicographically after structured_output, while a
// global section above the 190 band follows the capture instruction.
ctx.tools.register({
ctx.tools.register(defineContentToolFixture({
name: 'zz_probe',
description: 'probe',
parameters: { type: 'object', properties: {} },
parameters: {},
execute: () => Promise.resolve([{ type: 'text', text: 'x' }]),
})
}))
ctx.systemPrompt.section({ name: 'after-band', order: 200, text: 'AFTER-BAND' })
const run = await ctx.subagents.start('spawn', structuredRequest(parent))
await run.result
@@ -659,7 +666,7 @@ describe('in-process structured output', () => {
agent: parent,
})
expect(result.isError).toBe(true)
expect(result.error?.code).toBe('UNKNOWN_TOOL')
expect(result.error?.info?.code).toBe('UNKNOWN_TOOL')
})
it('a structured_output call with NO calling agent at all is UNKNOWN_TOOL', async () => {
@@ -671,7 +678,7 @@ describe('in-process structured output', () => {
arguments: { answer: 1 },
})
expect(result.isError).toBe(true)
expect(result.error?.code).toBe('UNKNOWN_TOOL')
expect(result.error?.info?.code).toBe('UNKNOWN_TOOL')
})
it('a failed execution stage is discarded and never promoted by a later call', async () => {