Skip to content

Commit 7ef52dc

Browse files
committed
fix(agentic): yield no return schema when conversion fails
Checking only for a converter's presence left the throwing path on the fallback: `input`/`output` may throw when conversion is unsupported, and that was caught into the permissive object schema, reproducing the same MCP rejection. Convert via the converter's `output` too, since a transforming validator returns its output type, and the return value is what `outputSchema` describes.
1 parent 948df8f commit 7ef52dc

2 files changed

Lines changed: 42 additions & 4 deletions

File tree

‎packages/devframe/src/agent/__tests__/to-json-schema.test.ts‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,35 @@ describe('returnToJsonSchema', () => {
5353
expect(returnToJsonSchema(v.object({ ok: v.boolean() }))).toBeUndefined()
5454
expect(returnToJsonSchema(v.array(v.object({ ok: v.boolean() })))).toBeUndefined()
5555
})
56+
57+
it('yields no schema when the converter cannot express the schema', () => {
58+
const throwing = {
59+
'~standard': {
60+
version: 1,
61+
vendor: 'test',
62+
validate: (value: unknown) => ({ value }),
63+
jsonSchema: {
64+
input: () => { throw new Error('unsupported') },
65+
output: () => { throw new Error('unsupported') },
66+
},
67+
} as StandardSchemaV1['~standard'],
68+
}
69+
expect(returnToJsonSchema(throwing)).toBeUndefined()
70+
})
71+
72+
it('converts the output type, not the input type', () => {
73+
const transforming = {
74+
'~standard': {
75+
version: 1,
76+
vendor: 'test',
77+
validate: (value: unknown) => ({ value }),
78+
jsonSchema: {
79+
input: () => ({ type: 'string' }),
80+
output: () => ({ type: 'object', properties: { parsed: { type: 'number' } } }),
81+
},
82+
} as StandardSchemaV1['~standard'],
83+
}
84+
expect(returnToJsonSchema(transforming))
85+
.toEqual({ type: 'object', properties: { parsed: { type: 'number' } } })
86+
})
5687
})

‎packages/devframe/src/agent/to-json-schema.ts‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ function safeToJsonSchema(schema: StandardSchemaV1): unknown {
3232
*
3333
* Unlike args, a return value has no permissive fallback: the schema is
3434
* advertised as an MCP `outputSchema`, which obliges the tool to return a
35-
* matching object on every call. A validator without a native converter
36-
* (e.g. valibot) yields no output schema rather than an unfounded object
37-
* one, so array- and primitive-returning tools still work.
35+
* matching object on every call. A validator with no native converter
36+
* (e.g. valibot), or one whose converter cannot express the schema, yields
37+
* no output schema rather than an unfounded object one, so array- and
38+
* primitive-returning tools still work. Conversion uses the converter's
39+
* `output`, since a transforming validator returns its output type.
3840
* @internal
3941
*/
4042
export function returnToJsonSchema(schema: StandardSchemaV1 | undefined): unknown {
@@ -43,7 +45,12 @@ export function returnToJsonSchema(schema: StandardSchemaV1 | undefined): unknow
4345
const standard = schema['~standard'] as MaybeJsonSchema
4446
if (!standard.jsonSchema)
4547
return undefined
46-
return safeToJsonSchema(schema)
48+
try {
49+
return standard.jsonSchema.output({ target: 'draft-2020-12' })
50+
}
51+
catch {
52+
return undefined
53+
}
4754
}
4855

4956
/**

0 commit comments

Comments
 (0)