subagent: implement structured output for in-process backends
The seam vocabulary (SubagentStartRequest.outputSchema, SubagentResult .structured) existed but no in-process backend honored it — spawn/fork advertised outputSchema: false. This lands the missing half: - dsh-tools gains a structured-output JSON Schema subset (json-schema.ts): StructuredOutputSchema, assertSupportedOutputSchema (rejects loud outside the enforced subset, every violation listed), validateStructuredValue (path-qualified issues, total). outputSchema's seam type becomes this raw JSON-Schema subset instead of the author-facing SchemaSpec DSL — the schema travels verbatim to the model as a forced tool's parameters. - dsh-subagent-inprocess gains the shared structured runtime: one global structured_output capture tool (placeholder parameters) + a prepend:true agent/request listener doing FINAL-REQUEST enforcement (strip for plain agents, per-run schema for structured children — survives downstream request-replacing listeners) + an agent/turn-continuation veto that stops a child's turn once captured (no wasted extra model step). Lifetime is refcounted by backends (plugin lifetime) AND live runs (start→settle). - startInProcessRun drives the capture: subset asserted before the child exists, instruction appended to the child's system prompt, clean-finish nudge loop (structuredNudgeRetries, backend Config, default 1), captured value on result.structured; a clean finish without a capture settles 'error' (never a silent success with a missing field). - spawn + fork flip outputSchema: true and inject 'tools'.
This commit is contained in:
@@ -41,13 +41,13 @@ describe('dsh-subagent-mock', () => {
|
||||
|
||||
it('surfaces a structured result when the request carries an outputSchema', async () => {
|
||||
const ctx = await mount({ reply: 'r', structured: { answer: 42 } })
|
||||
const run = ctx.subagents.start('mock', baseRequest({ outputSchema: { answer: { type: 'number' } } }))
|
||||
const run = ctx.subagents.start('mock', baseRequest({ outputSchema: { type: 'object', properties: { answer: { type: 'number' } } } }))
|
||||
await expect(run.result).resolves.toMatchObject({ structured: { answer: 42 } })
|
||||
})
|
||||
|
||||
it('defaults structured output to { reply } when outputSchema is requested but no structured value is configured', async () => {
|
||||
const ctx = await mount({ reply: 'fallback reply' })
|
||||
const run = ctx.subagents.start('mock', baseRequest({ outputSchema: { answer: { type: 'number' } } }))
|
||||
const run = ctx.subagents.start('mock', baseRequest({ outputSchema: { type: 'object', properties: { answer: { type: 'number' } } } }))
|
||||
await expect(run.result).resolves.toMatchObject({ structured: { reply: 'fallback reply' } })
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user