Merge branch 'codex/code-mode-typed-results' into codex/code-mode-complete-result-card
# Conflicts: # .agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.i18n.yaml # .agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml
This commit is contained in:
@@ -7,7 +7,7 @@ import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import { CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
|
||||
import type { CodeRunRequest, CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
|
||||
import ToolRegistry, { CodeRunFailedError, RUN_CODE_NAME, TOOL_ABORTED_BEFORE_DISPATCH, defineContentToolFixture, defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import type { Config, PostToolDecision, ToolExecutionResult } from '@deepseek-ai/dsh-tools'
|
||||
import type { Config, JsonSchemaNode, PostToolDecision, ToolExecutionResult } from '@deepseek-ai/dsh-tools'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { Session, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { JsonValue, SessionEventMap } from '@deepseek-ai/dsh-session'
|
||||
@@ -130,6 +130,30 @@ describe('mode-aware wire contribution', () => {
|
||||
expect(sdk?.text).not.toContain('run_code:')
|
||||
})
|
||||
|
||||
it('projects deeply nested output schemas into the Code Mode SDK without structured-clone recursion', async () => {
|
||||
const { ctx, systemPrompt } = await setup({ mode: 'code' })
|
||||
let output: JsonSchemaNode = { type: 'string' }
|
||||
for (let depth = 0; depth < 5_000; depth++) {
|
||||
output = { oneOf: [output, { type: 'null' }] }
|
||||
}
|
||||
ctx.tools.register({
|
||||
name: 'deep_output',
|
||||
description: 'Return a deeply nested output union.',
|
||||
parameters: { type: 'object', properties: {} },
|
||||
output: {
|
||||
schema: output,
|
||||
render: (_args, value) => [{ type: 'text', text: typeof value === 'string' ? value : 'null' }],
|
||||
},
|
||||
execute() { return Promise.resolve('ok') },
|
||||
})
|
||||
|
||||
const assembly = await systemPrompt.assemble()
|
||||
const sdk = assembly.sections.find(section => section.name === 'tools:sdk')?.text
|
||||
|
||||
expect(sdk).toContain('deep_output: Record<string, JsonValue>;')
|
||||
expect(sdk).toContain('deep_output: string | null')
|
||||
})
|
||||
|
||||
it.each(['code', 'both'] as const)('treats expert assembly output as authoritative in mode %s', async (mode) => {
|
||||
const { ctx, systemPrompt } = await setup({ mode })
|
||||
registerEcho(ctx)
|
||||
@@ -272,6 +296,10 @@ describe('mode-aware wire contribution', () => {
|
||||
const { ctx, runtime } = await setup({ mode: 'both' })
|
||||
registerEcho(ctx)
|
||||
runtime.behavior = (request) => {
|
||||
expect(request.bindings[0]!.errorClass).toEqual({
|
||||
name: 'ToolCallError',
|
||||
memberNameProperty: 'toolName',
|
||||
})
|
||||
const functions = request.bindings[0]!.functions
|
||||
return Promise.resolve({
|
||||
logs: [],
|
||||
@@ -835,6 +863,57 @@ describe('the run_code dispatch bridge', () => {
|
||||
expect(events.filter(event => event.type === 'tool/code-dispatch')).toEqual([])
|
||||
})
|
||||
|
||||
it('dispatches and durably logs binding arguments deeper than the structured-clone call stack', async () => {
|
||||
const { ctx, runtime } = await setup({ mode: 'code' })
|
||||
const depth = 5_000
|
||||
let observedDepth = 0
|
||||
let observedLeaf: JsonValue | undefined
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'deep_args',
|
||||
description: 'Measure a deeply nested JSON argument.',
|
||||
parameters: { nested: { type: 'json', required: true } },
|
||||
output: {
|
||||
schema: { type: 'integer' },
|
||||
render: (_args, value) => [{ type: 'text', text: String(value) }],
|
||||
},
|
||||
execute(args) {
|
||||
let cursor = args.nested
|
||||
while (Array.isArray(cursor)) {
|
||||
if (cursor.length !== 1) throw new Error('expected one item per nesting layer')
|
||||
observedDepth++
|
||||
cursor = cursor[0]!
|
||||
}
|
||||
observedLeaf = cursor
|
||||
return Promise.resolve(observedDepth)
|
||||
},
|
||||
}))
|
||||
const session = new Session(SessionId('deep-code-arguments'))
|
||||
const agent = { session } as Agent
|
||||
runtime.behavior = async (request) => {
|
||||
let nested: JsonValue = 'leaf'
|
||||
for (let index = 0; index < depth; index++) nested = [nested]
|
||||
const value = await request.bindings[0]!.functions.deep_args!({ nested })
|
||||
return { logs: [], value }
|
||||
}
|
||||
|
||||
const result = await runCode(ctx, 'return tools.deep_args(...)', { agent })
|
||||
|
||||
expect(result.isError).toBe(false)
|
||||
expect(result.isError ? undefined : result.value).toEqual({ logs: [], result: depth })
|
||||
expect({ observedDepth, observedLeaf }).toEqual({ observedDepth: depth, observedLeaf: 'leaf' })
|
||||
const dispatch = session.events.find(event => event.type === 'tool/code-dispatch')
|
||||
if (dispatch === undefined) throw new Error('expected a durable tool/code-dispatch event')
|
||||
const logged = dispatch.data.arguments as { nested: JsonValue }
|
||||
let loggedDepth = 0
|
||||
let loggedCursor = logged.nested
|
||||
while (Array.isArray(loggedCursor)) {
|
||||
if (loggedCursor.length !== 1) throw new Error('expected one logged item per nesting layer')
|
||||
loggedDepth++
|
||||
loggedCursor = loggedCursor[0]!
|
||||
}
|
||||
expect({ loggedDepth, loggedCursor }).toEqual({ loggedDepth: depth, loggedCursor: 'leaf' })
|
||||
})
|
||||
|
||||
it('gives the tool and durable log the same immutable argument value', async () => {
|
||||
const { ctx, runtime } = await setup({ mode: 'code' })
|
||||
const { agent, events } = fakeAgent()
|
||||
|
||||
@@ -30,6 +30,21 @@ function violationsOf(schema: unknown, objectRoot = false): string[] {
|
||||
throw new Error('expected schema rejection')
|
||||
}
|
||||
|
||||
function recordWithForgedIntrinsicPrototype(
|
||||
own: Record<string, unknown>,
|
||||
inherited: Record<string, unknown> = {},
|
||||
revoked = false,
|
||||
): Record<string, unknown> {
|
||||
const prototype = Object.assign(Object.create(null) as Record<string, unknown>, inherited)
|
||||
const ForgedObject = function ForgedObject(): void {}
|
||||
Object.defineProperty(ForgedObject, 'name', { value: 'Object' })
|
||||
ForgedObject.prototype = prototype
|
||||
const constructor = revoked ? Proxy.revocable(ForgedObject, {}) : undefined
|
||||
if (constructor !== undefined) constructor.revoke()
|
||||
Object.defineProperty(prototype, 'constructor', { value: constructor?.proxy ?? ForgedObject })
|
||||
return Object.assign(Object.create(prototype) as Record<string, unknown>, own)
|
||||
}
|
||||
|
||||
describe('the enforced raw JSON Schema subset', () => {
|
||||
it('accepts every JSON root and every supported node', () => {
|
||||
for (const schema of [
|
||||
@@ -81,6 +96,23 @@ describe('the enforced raw JSON Schema subset', () => {
|
||||
.toEqual(['schema.items is not supported beside oneOf'])
|
||||
expect(violationsOf({ oneOf: [{ type: 'string' }, { type: 'weird' }] })[0])
|
||||
.toContain('schema.oneOf[1].type')
|
||||
const sparse = new Array<unknown>(2)
|
||||
sparse[0] = { type: 'string' }
|
||||
expect(violationsOf({ oneOf: sparse }))
|
||||
.toEqual(['schema.oneOf must be an array of at least two schemas'])
|
||||
const compensatedSparse = new Array<unknown>(2)
|
||||
compensatedSparse[0] = { type: 'string' }
|
||||
Object.defineProperty(compensatedSparse, 'extra', { value: true })
|
||||
expect(violationsOf({ oneOf: compensatedSparse }))
|
||||
.toEqual(['schema.oneOf must be an array of at least two schemas'])
|
||||
class ExoticBranches extends Array<unknown> {}
|
||||
expect(violationsOf({ oneOf: new ExoticBranches({ type: 'string' }, { type: 'null' }) }))
|
||||
.toEqual(['schema.oneOf must be an array of at least two schemas'])
|
||||
const explosiveArray = new Proxy([{ type: 'string' }, { type: 'null' }], {
|
||||
getPrototypeOf() { throw new Error('prototype trap') },
|
||||
})
|
||||
expect(violationsOf({ oneOf: explosiveArray }))
|
||||
.toEqual(['schema.oneOf must be an array of at least two schemas'])
|
||||
})
|
||||
|
||||
it('rejects unknown and misplaced keywords without accepted-then-ignored behavior', () => {
|
||||
@@ -134,6 +166,9 @@ describe('the enforced raw JSON Schema subset', () => {
|
||||
'schema.properties must be an object of schemas',
|
||||
'schema.required names "missing" which is not in properties',
|
||||
])
|
||||
const sparseRequired = new Array<string>(1)
|
||||
expect(violationsOf({ type: 'object', required: sparseRequired }))
|
||||
.toEqual(['schema.required must be an array of strings'])
|
||||
})
|
||||
|
||||
it('requires type-correct scalar enum and const values', () => {
|
||||
@@ -163,6 +198,9 @@ describe('the enforced raw JSON Schema subset', () => {
|
||||
.toEqual(['schema.enum must be a non-empty array of string values'])
|
||||
expect(violationsOf({ type: 'string', enum: ['a'], const: 'b' }))
|
||||
.toEqual(['schema.const must be one of schema.enum when both are declared'])
|
||||
const sparseEnum = new Array<string>(1)
|
||||
expect(violationsOf({ type: 'string', enum: sparseEnum }))
|
||||
.toEqual(['schema.enum must be a non-empty array of string values'])
|
||||
})
|
||||
|
||||
it('validates annotation types and lossless JSON payloads', () => {
|
||||
@@ -195,6 +233,8 @@ describe('the enforced raw JSON Schema subset', () => {
|
||||
it('accepts lossless annotation containers from another JavaScript realm', () => {
|
||||
const schema = runInNewContext(`({
|
||||
type: 'object',
|
||||
properties: { value: { type: 'string', enum: ['x'] } },
|
||||
required: ['value'],
|
||||
default: { x: 1 },
|
||||
examples: [[{ ok: true }]],
|
||||
})`) as unknown
|
||||
@@ -212,6 +252,28 @@ describe('the enforced raw JSON Schema subset', () => {
|
||||
.toEqual(['schema.properties must be an object of schemas'])
|
||||
expect(violationsOf({ type: 'object', properties: { at: new Date(0) } }))
|
||||
.toEqual(['schema.properties.at must be a schema object'])
|
||||
|
||||
const forgedSchema = recordWithForgedIntrinsicPrototype(
|
||||
{ type: 'object' },
|
||||
{ oneOf: [{ type: 'string' }, { type: 'null' }] },
|
||||
)
|
||||
expect(violationsOf(forgedSchema)).toEqual(['schema must be a schema object'])
|
||||
expect(violationsOf(forgedSchema, true)).toEqual(['schema must be a schema object'])
|
||||
expect(violationsOf(recordWithForgedIntrinsicPrototype({ type: 'string' }, {}, true)))
|
||||
.toEqual(['schema must be a schema object'])
|
||||
const prototypeWithoutConstructor = Object.create(null) as object
|
||||
expect(violationsOf(Object.create(prototypeWithoutConstructor) as unknown))
|
||||
.toEqual(['schema must be a schema object'])
|
||||
expect(violationsOf(Object.defineProperty({ type: 'string' }, 'hidden', { value: true })))
|
||||
.toEqual(['schema must be a schema object'])
|
||||
expect(violationsOf({ type: 'string', [Symbol('hidden')]: true }))
|
||||
.toEqual(['schema must be a schema object'])
|
||||
expect(violationsOf(new Proxy({}, {
|
||||
getPrototypeOf() { throw new Error('prototype trap') },
|
||||
}))).toEqual(['schema must be a schema object'])
|
||||
expect(violationsOf(new Proxy({}, {
|
||||
ownKeys() { throw new Error('keys trap') },
|
||||
}))).toEqual(['schema must be a schema object'])
|
||||
})
|
||||
|
||||
it('asserts deeply nested raw unions without using the JavaScript call stack', () => {
|
||||
@@ -369,6 +431,21 @@ describe('validateJsonSchemaValue', () => {
|
||||
asserted({ type: 'object', properties: { constructor: { type: 'string' } } }),
|
||||
{},
|
||||
)).toEqual([])
|
||||
|
||||
const inheritedUnion = Object.assign(
|
||||
Object.create({ oneOf: [{ type: 'string' }, { type: 'null' }] }) as JsonSchemaNode,
|
||||
{ type: 'object' as const },
|
||||
)
|
||||
expect(validateJsonSchemaValue(inheritedUnion, {})).toEqual([])
|
||||
expect(validateJsonSchemaValue(inheritedUnion, 'x')).toEqual(['"value" must be an object'])
|
||||
expect(validateJsonSchemaValue(
|
||||
{ type: 'object', properties: undefined } as unknown as JsonSchemaNode,
|
||||
{},
|
||||
)).toEqual([])
|
||||
expect(validateJsonSchemaValue(
|
||||
{ type: 'object', required: undefined } as unknown as JsonSchemaNode,
|
||||
{},
|
||||
)).toEqual([])
|
||||
})
|
||||
|
||||
it('keeps assertNever as a forged-schema backstop', () => {
|
||||
|
||||
@@ -71,6 +71,7 @@ describe('the unified author schema DSL', () => {
|
||||
{ type: 'string', oneOf: [{ type: 'string' }, { type: 'null' }] },
|
||||
{ oneOf: 'not-an-array' },
|
||||
{ type: 'string', enum: 'a' },
|
||||
{},
|
||||
null,
|
||||
]) {
|
||||
expect(() => valueSchemaSpecToJsonSchema(schema as ValueSchemaSpec), JSON.stringify(schema)).toThrow(JsonSchemaError)
|
||||
@@ -80,6 +81,24 @@ describe('the unified author schema DSL', () => {
|
||||
} as unknown as ParameterSchemaSpec)).toThrow(JsonSchemaError)
|
||||
expect(() => parameterSchemaSpecToJsonSchema(null as unknown as ParameterSchemaSpec)).toThrow(JsonSchemaError)
|
||||
expect(() => parameterSchemaSpecToJsonSchema({ bad: 42 } as unknown as ParameterSchemaSpec)).toThrow(JsonSchemaError)
|
||||
|
||||
const symbolKey = Symbol('hidden')
|
||||
expect(() => parameterSchemaSpecToJsonSchema({
|
||||
value: { type: 'string' },
|
||||
[symbolKey]: { type: 'number' },
|
||||
} as unknown as ParameterSchemaSpec)).toThrow(JsonSchemaError)
|
||||
const hiddenKey = Object.defineProperty({ value: { type: 'string' } }, 'hidden', {
|
||||
value: { type: 'number' },
|
||||
})
|
||||
expect(() => parameterSchemaSpecToJsonSchema(hiddenKey as ParameterSchemaSpec)).toThrow(JsonSchemaError)
|
||||
const sparseOneOf = new Array<ValueSchemaSpec>(2)
|
||||
sparseOneOf[0] = { type: 'string' }
|
||||
expect(() => valueSchemaSpecToJsonSchema({ oneOf: sparseOneOf } as unknown as ValueSchemaSpec)).toThrow(JsonSchemaError)
|
||||
const decoratedEnum = Object.assign(['a'], { hidden: true })
|
||||
expect(() => valueSchemaSpecToJsonSchema({
|
||||
type: 'string',
|
||||
enum: decoratedEnum,
|
||||
})).toThrow(JsonSchemaError)
|
||||
})
|
||||
|
||||
it('rejects cyclic author schemas', () => {
|
||||
@@ -143,6 +162,22 @@ describe('the unified author schema DSL', () => {
|
||||
}>>().toEqualTypeOf<{ id: number } & Record<string, JsonValue>>()
|
||||
})
|
||||
|
||||
it('bounds inference for deeply nested author schemas', () => {
|
||||
type Repeat<Count extends number, Result extends unknown[] = []> =
|
||||
Result['length'] extends Count ? Result : Repeat<Count, [unknown, ...Result]>
|
||||
type DeepArraySchema<Levels extends unknown[]> =
|
||||
Levels extends [unknown, ...infer Rest]
|
||||
? { type: 'array'; items: DeepArraySchema<Rest> }
|
||||
: { type: 'string' }
|
||||
type PeelArrays<Value, Levels extends unknown[]> =
|
||||
Levels extends [unknown, ...infer Rest]
|
||||
? Value extends (infer Item)[] ? PeelArrays<Item, Rest> : never
|
||||
: Value
|
||||
|
||||
type DeepValue = InferValue<DeepArraySchema<Repeat<50>>>
|
||||
expectTypeOf<PeelArrays<DeepValue, Repeat<16>>>().toEqualTypeOf<JsonValue>()
|
||||
})
|
||||
|
||||
it('infers required and optional parameter keys', () => {
|
||||
expectTypeOf<InferArgs<{
|
||||
path: { type: 'string'; required: true }
|
||||
@@ -152,6 +187,7 @@ describe('the unified author schema DSL', () => {
|
||||
})
|
||||
|
||||
it('makes invalid author forms compile-time errors', () => {
|
||||
const symbolKey = Symbol('parameter')
|
||||
const invalidObjects = {
|
||||
// @ts-expect-error explicit object schemas require an openness decision
|
||||
object: { type: 'object' } satisfies ValueSchemaSpec,
|
||||
@@ -161,7 +197,9 @@ describe('the unified author schema DSL', () => {
|
||||
enum: { type: 'number', enum: ['1'] } satisfies ValueSchemaSpec,
|
||||
// @ts-expect-error parameter requiredness is true-or-absent
|
||||
required: { value: { type: 'string', required: false } } satisfies ParameterSchemaSpec,
|
||||
// @ts-expect-error parameter maps accept string keys only
|
||||
symbol: { [symbolKey]: { type: 'string' } } satisfies ParameterSchemaSpec,
|
||||
}
|
||||
expect(Object.keys(invalidObjects)).toHaveLength(4)
|
||||
expect(Object.keys(invalidObjects)).toHaveLength(5)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1588,6 +1588,55 @@ describe('ToolRegistry', () => {
|
||||
expect(result.content[0]).toMatchObject({ text: 'short-circuited' })
|
||||
})
|
||||
|
||||
it('revalidates a cached canonical result returned from a different dispatch', async () => {
|
||||
const ctx = await setup()
|
||||
ctx.tools.register({ ...echoTool, name: 'string-output', async execute() { return 'cached' } })
|
||||
let objectBodyRan = false
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'object-output',
|
||||
description: 'Return one closed object.',
|
||||
parameters: {},
|
||||
output: {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: { ok: { type: 'boolean', required: true } },
|
||||
additionalProperties: false,
|
||||
},
|
||||
render: (_args, value) => [{ type: 'text', text: String(value.ok) }],
|
||||
},
|
||||
execute() {
|
||||
objectBodyRan = true
|
||||
return Promise.resolve({ ok: true })
|
||||
},
|
||||
}))
|
||||
let cached: ToolExecutionResult | undefined
|
||||
ctx.on('tools/execute', async (exec, next) => {
|
||||
if (exec.name === 'string-output') {
|
||||
cached = await next()
|
||||
return cached
|
||||
}
|
||||
if (exec.name === 'object-output') {
|
||||
if (cached === undefined) throw new Error('expected the first dispatch result')
|
||||
return cached
|
||||
}
|
||||
return next()
|
||||
})
|
||||
|
||||
const first = await ctx.tools.execute({
|
||||
signal: testToolSignal, callId: CallId('cached-first'), name: 'string-output', arguments: {},
|
||||
})
|
||||
const second = await ctx.tools.execute({
|
||||
signal: testToolSignal, callId: CallId('cached-second'), name: 'object-output', arguments: {},
|
||||
})
|
||||
|
||||
expect(first.isError ? undefined : first.value).toBe('cached')
|
||||
expect(objectBodyRan).toBe(false)
|
||||
expect(second).toMatchObject({
|
||||
isError: true,
|
||||
error: { info: { name: 'ToolOutputError', code: 'INVALID_TOOL_OUTPUT' } },
|
||||
})
|
||||
})
|
||||
|
||||
it('preserves additionalContexts supplied by an around-dispatch result', async () => {
|
||||
const ctx = await setup()
|
||||
ctx.tools.register(echoTool)
|
||||
|
||||
Reference in New Issue
Block a user