cordis_define / cordis_inspect_query: object/oneOf tool-call params arrive as raw strings, not parsed objects #4747
Replies: 4 comments
Second independent confirmation of the "one related but distinct issue" aboveReproduced again, separately, after the fix from the main report was applied and the
Traced independently to the same spot flagged above: Not something we could resolve from the calling side — flagging as confirmed-reproducible for whoever has context on that boundary. |
|
Confirmed the mechanism, with one correction to the framing that changes where a fix belongs. The top-level arguments are parsed — the gap is per-property
function parseArguments(raw: string): unknown {
try { return raw ? JSON.parse(raw) : {} } catch { return raw }
}So That distinction matters: the validator is not wrong, and neither is the parse. What is missing is any tolerance for a model that stringifies nested values. I checked for that tolerance and there is none anywhere in the path — Why your two symptoms differThey are the same cause at different layers, which is why the messages do not match:
Anything that adds coercion has to cover both, or you will fix half of what you reported and the other half will look unchanged. The design question, stated honestlyCoercion is not free. "If the schema wants an object and the value is a string that parses as JSON, parse it" would fix your case, and it would also silently accept a genuinely wrong argument whenever a model passes a JSON-looking string where a string was meant. A tool whose parameter is legitimately The narrow version that seems defensible: coerce only when no branch of the declared schema accepts a string and the value parses to something that does. That keeps a real string parameter untouched, because a string branch exists and matches first. A bare Worth noting the schemas you hit are the ones most likely to trigger it — a property with no top-level (Version check worth doing regardless: |
|
I checked the rc.2 source and agree with the correction already made above: the outer call is parsed once, and the shared validator is correctly rejecting the value it receives.
return raw ? JSON.parse(raw) : {}So I would keep three repairs separate:
The critical invariant is that presentation, human approval, validation, and execution all use the same normalized argument identity. Otherwise a user can approve one visible value while a different value executes. We published the full normalization gates and regression matrix here: Pinned source: |
|
Thanks both — this is a real correction, not just confirmation, and I want to flag where I agree rather than let it read as settled. Agreeing with the refinement@nokkies is right that the fix I described is broader than it should be. My patch coerces a string-to-object whenever the schema has no top-level Agreeing on where it should live@denial123789's point about not mutating the validator frame in place is also a fair critique of what I actually shipped — I did exactly that (mutate What I can't currently verifyI don't have a env set up right now to test the narrower "no string branch" condition against both the One correction of my own: the linked handbook page ( |
Uh oh!
There was an error while loading. Please reload this page.
Summary
Model-supplied tool-call parameters whose JSON Schema has no top-level
type— i.e. a bareoneOf(likecordis_define'splugin) or atype: "json"annotation (likecordis_inspect_query'sinput) — arrive atexecute()as a raw string instead of a parsed object/array, and are rejected by the argument validator. This makescordis_defineand half ofcordis_inspect_queryunusable with at least one Ollama-routed cloud model (glm-5.3-flash:cloud).Repro
dshweb profile, Creator mode, modelglm-5.3-flash:cloud(via thellm-pi-ai→ Ollamaopenai-completionsroute).cordis_define) — e.g. the example flow in thecordis-plugin-developmentskill (plugin: { kind: "new", idPrefix: "clock" }).cordis_definefails every time with:Tool.listTools's live schema forcordis_definemid-session).cordis_inspect_querywith{"root": "tool.view.cordis"}:Root cause (verified by reading the installed package, not inferred)
packages/core/tools(installed as@deepseek-ai/dsh-tools@0.1.1-rc.2,lib/index.js), the argument walker/validator (checkValue→case "object"/case "array"):By the time this runs,
frame.valuefor the affected parameter is a string containing valid JSON (e.g."{\"kind\":\"new\",\"idPrefix\":\"clock\"}"), not an object — even though the outer tool-call arguments blob already went through oneJSON.parseindsh-agent-loop'sparseArguments(raw). So the model is emitting that specific nested parameter double-encoded (as a JSON string inside the outer JSON), and nothing downstream re-parses it.The parameters that are affected have one thing in common: they have no top-level
typein their model-facing schema —pluginis a bareoneOf(dsh-tool-cordis/lib/index.js,cordis_define'sparameters.plugin),inputis atype: "json"annotation-only node. Parameters with an explicittype: "object"/type: "array"at the top level (e.g.todo_write'stodos) are unaffected — they arrive already parsed. My working theory is that whatever renders/normalizes the parameter schema surfaced to this particular model loses the "this must be a structured value" signal for the no-top-level-typecases, and the model falls back to stringifying it.Suggested fix (tested locally, works)
Purely defensive, harness-side, and low-risk (only activates on what would otherwise be a guaranteed rejection): when a value destined for an
object/arrayschema position is astring, attemptJSON.parseand use the parsed result if it structurally matches, writing the coerced value back into the parent container (not just a local copy) so the tool'sexecute(args, ...)actually receives the fixed value, not just a validator-local one.Two things need patching together, both in
lib/index.js'scheckValue:case "object"/case "array"branches themselves (defends the root-argument-is-a-string edge case).frame.value[key]/ the array slot itself, or the fix only makes validation pass whileexecute()still receives the original unparsed string.After applying both, in the same session with the same model:
cordis_inspect_querywith{"root": "tool.view.cordis"}succeeded on the first try (previously failed 3/3).cordis_define'spluginargument passed validation (previously failed 100% of attempts with the oneOf error) and reachedexecute()correctly shaped.One related but distinct issue found afterward
With the above fixed,
cordis_definecan still fail withno dynamic plugin "undefined" in this process — it may have been removed or lost on DSH restarton some calls, which traces todsh-cordis-host-runnertreatingargs.plugin.kindas not"new"at the point it builds the request — even though the tool-layerargs.pluginobject was, this time, correctly typed. This looks like a separate bug (possibly in how the args cross the tool→runner RPC boundary), not something the argument-coercion fix above addresses. Flagging it here rather than opening a second discussion since it surfaced while reproducing this one and may share a cause once someone with runner-internals context looks at it.Environment
@deepseek-ai/dsh0.1.1-rc.2(npm global install)@deepseek-ai/dsh-tools0.1.1-rc.2v24.16.0, Windows 11glm-5.3-flash:cloudvia thellm-pi-aiOllama route (api: openai-completions,baseURL: http://localhost:11434/v1)deepseek-v4-pro:cloudon the same route before the fix — thecordis_inspect_queryfailure was not model-specific.Happy to share the exact patch diff if useful — kept it out of this report to stay focused on repro + root cause first.
All reactions