Repository navigation
fix: send valid tool schemas and non-empty reasoning replay to OpenAI-compatible relays - #900
Merged
Merged
Conversation
TypeBox 1.x omits `required` altogether when no property is required, and Read, Glob, Grep, and BrowserPreview declare their canonical argument as optional so the `file_path` / `query` alias spelling validates too. The OpenAI chat/completions adapter forwards `parameters` verbatim — only the Anthropic adapter fills `required ?? []` — so a relay that decodes the missing key into a nil slice answered `Invalid schema for function 'Read': null is not of type "array"` (#864). Normalise the schema where the tool catalogue is built, because that is the single origin of every tool: the session agent and a delegated Task run then send the same declaration. An absent `required` already means "nothing is required", so writing `[]` states the same contract explicitly and changes no tool's accepted arguments.
The 0.86.1 patch taught convertMessages to fill a documented placeholder when a DeepSeek-family relay rejects an empty reasoning echo, but pi-ai rebuilds compat from an explicit allowlist in getCompat. The flag that provider-binding puts on a relayed DeepSeek model never reached the adapter, so the fill silently degraded to "" and the relay rejected the request with `The reasoning_content in the thinking mode must be passed back to the API` (#864). Pass the opt-in through getCompat and declare it on OpenAICompletionsCompat. No endpoint can be auto-detected as requiring a non-empty echo, so detectCompat reports it as undefined and only an explicit model.compat opt-in enables it. fixes #864
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #864.
Two independent causes behind
PROVIDER_ERRORagainst an OpenAI-compatiblerelay (
ps.air-outer.com+deepseek-v4-flash).1.
Readreached the relay with norequiredkey at all.Read,Glob,Grep, andBrowserPreviewdeclare their canonical argumentas
Type.Optionalso thefile_path/queryalias spelling validates too;TypeBox 1.x then omits
requiredentirely. The OpenAI chat/completions adapterforwards
parametersverbatim (only the Anthropic adapter fillsrequired ?? []), so a relay that decodes the missing key into a nil sliceanswered
Invalid schema for function 'Read': null is not of type "array".Neither the relay nor PI-Desktop ever emitted a literal
null.The schema is now normalised in
rebuildToolCatalog(), the single origin ofevery tool, so the session agent and a delegated
Taskrun send the samedeclaration. An absent
requiredalready means "nothing is required", sowriting an empty array changes no tool's accepted arguments.
2. The non-empty reasoning replay never reached the wire.
The 0.86.1 patch taught
convertMessagesto fill a documented placeholder forrelays that reject an empty reasoning echo, but pi-ai rebuilds compat from an
explicit allowlist in
getCompat, so the flagprovider-bindingputs on arelayed DeepSeek model was dropped. The fill silently degraded to an empty
string, which strict relays reject with
The reasoning_content in the thinking mode must be passed back to the API. The opt-in now passes throughgetCompatand is declared on
OpenAICompletionsCompat. No endpoint can be auto-detectedas requiring a non-empty echo, so it stays an explicit
model.compatopt-in.Validation
Base:
6ad892d806afpnpm build:js— passed (includingapps/desktop)pnpm --filter @pi-desktop/desktop typecheck— passedpnpm -r --if-present test— passedpnpm lint— passednode scripts/check-architecture.mjs— passed (2 new files, 40 / 52 LOC)pnpm check:pr-base— passedEach new test was confirmed to fail before its fix:
Read.requiredabsent:Read: expected { Object (type, properties) } to match object { type: 'object', required: [] }on both the session and thedelegate path
getCompatpassthrough removed:expected '' to be '[reasoning not retained for this turn]'The reasoning test drives a real request through
buildProviderModel→ pi-aigetCompat→ the adapter and asserts the captured outbound body, because theexisting backfill test hands a hand-built
compatstraight toconvertMessagesand therefore cannot see a dropped flag.Not run:
verify:ui:*E2E. These are wire-format defects proven at the adapterboundary — the reasoning test asserts the actual request body — and the UI E2E
suites require an explicit request per repository policy.