- Classified model_not_available_for_integrator as a permanent entitlement denial instead of transient fleet skew.
- Preserved the provider response and Available models list while retaining model_not_supported fleet retries.
Fixes#7819
- Implemented in-house, zero-dependency utility modules in `pi-utils` covering DOM manipulation, markdown parsing, templating, browser automation helpers, and terminal buffers.
- Migrated packages across the repository to consume the new internal utilities and `omptype` schema validators instead of external dependencies.
- Removed multiple external runtime and development dependencies including Zod, Marked, LRU cache, Turndown, and Puppeteer browser packages.
The 3 -> 8 bump in callWithCopilotModelRetry was shared by the generic
retryable branch, so a persistent status-less transport blip on Copilot
would ramp across 8 attempts (~11.2s of dead time) instead of the 3 it
took before, and a repeated Retry-After 429 could stretch the same way
on top of the transport's own fetchWithRetry budget.
Derive the budget from the failure kind: model-availability 400s keep the
8-attempt reroll, everything else caps at the previous 3.
Also read COPILOT_TRANSIENT_MODEL_CODES with Object.hasOwn — `code` is
provider-controlled, so a 400 body whose code was `__proto__` or
`toString` classified as transient through the prototype chain.
Any Copilot model in the middle of a rollout (claude-sonnet-4.6,
claude-opus-4.6, gpt-5.4, gpt-5.3-codex, ...) returned a raw HTTP 400 on
roughly half of all turns. GET /models on api.githubcopilot.com returns
two different catalogs across repeated calls: part of the fleet serves
those ids, part rejects them with
400 {"error":{"message":"The requested model is not available for
integrator \"copilot-language-server\". ...",
"code":"model_not_available_for_integrator", ...}}
The absorb machinery already existed and was correct
(isCopilotTransientModelError -> isProviderRetryableError -> the
Anthropic transport's PROVIDER_MAX_RETRIES). Only the classifier missed:
it matched the older model_not_supported code and probed err.code /
err.error.code, while the real code is model_not_available_for_integrator
sitting at err.error.error.code (the SDK stores the parsed body on
.error, and Copilot's body is itself {error:{code}}). So
isProviderRetryableError fell through to "4xx => terminal".
Fix the classifier: providerErrorCode() walks the error envelope up to
depth 3 instead of hardcoding a shape, both Copilot model-availability
codes are accepted, and a wire-body text match backs it up because SDK
envelope shapes drift between provider families while the stringified
message does not. This alone restores the retry path, because
isProviderRetryableError consults the provider hook before its
4xx short-circuit.
Retry shape, since a rejection is a per-request replica reroll rather
than upstream backpressure:
- both transports wait a flat delay between model-flap attempts instead
of the growing backoff; generic retryable failures (429/5xx/transport)
keep their linear ramp and Retry-After handling
- the OpenAI-transport budget goes 3 -> 8 attempts, because a measured
~70% flap window produced a turn that needed 6 wire attempts and
exhausting the budget escalates to the agent-level retry, which
restarts the whole turn
Absorbed attempts cost no tokens: rejections are gateway-side, carry no
usage block, and return in ~208ms median versus ~1884ms for a served
request.
Also refresh the exhausted-retry guidance text, which cited a
nonexistent model id and described the cause as a per-client rollout gap
rather than fleet skew.
- Introduce `@oh-my-pi/omptype` as a new ArkType-compatible schema validation package featuring a lazy JIT runtime, JSON Schema emission, and compatibility adapters.
- Replace `arktype` across workspace packages and test utilities with `@oh-my-pi/omptype`.
- Add benchmark suites, tests, and documentation for the new validation engine and adapters.
- Update workspace build, test runner, and release configurations to include the new package.
- Added shared Python call and literal serialization utilities with multiline verbatim support.
- Standardized tool inventories to format as an OpenAI-Harmony functions namespace using TypeScript declarations.
- Updated tool normalization and rendering functions to accept options objects and default to Python-syntax examples.
- Refactored Gemini dialect rendering to leverage shared serialization functions directly.
- Added custom `coworkFetch` transport for ordered headers and decompression support in Anthropic requests.
- Updated Anthropic and Claude runtime versions along with request headers to replicate Cowork desktop profiles.
- Configured default Anthropic message stream fetches to use the new cowork fetch transport and TLS profiles.
- Updated alignment tests and documentation to reflect the new Cowork request configurations and header rules.
- Increased codex websocket first event timeout default to 300 seconds.
- Increased default stream idle and first event timeout thresholds to 300 seconds.
- Updated stream timeout test expectations to match new 300s global default.
Current Cursor CLI builds emit exec frames this client did not model. A
frame whose oneof number is absent from `agent.proto` decodes with
`message.case` unset, so the dispatcher found no handler, ran no tool and
sent no result — the server was left waiting on an execution that never
happened.
Every recognised frame now gets a typed answer:
- The seven Pi tools (45-51) run their local equivalents. They are a
separate wire family from the legacy args, not aliases: `pi_grep`'s
`ignore_case` is the inverse of the local `case` flag, `pi_find`
searches filenames (so it routes to `glob`, not `grep`), and
`pi_edit`'s replacements are renamed to snake_case pairs.
- Hooks, subagents, prechecks, MCP state, smart-mode, canvas,
conversation search and agent-store answer with the error, not-found or
empty-but-valid variant that is true of this client.
- Unnameable frames raise `ExecClientControlMessage.throw`
(`unknown_exec_variant`); recognised frames with no truthful answer —
`git_diff_request`, whose `GetDiffResponse` has no error variant —
raise `exec_variant_unsupported`.
Four frames previously answered `create(XSchema, {})`. In proto3 that is
not an empty result: the oneof is unset and the server reads it as "the
tool ran and produced nothing", indistinguishable from success. They now
send real variants.
`connect_scm` lost its repository (the target rides in a oneof, so the
flat property was always undefined) and settled on a fixed failure at the
announcement, before the server's `success`/`error`/`rejected` verdict
arrived on the completion frame.
The stream decoder tracked a single "current" tool-call block and settled
it on any `toolCallCompleted`, ignoring the envelope `call_id`: an
unrelated completion paired the wrong block, and `start A, start B`
orphaned A so nothing ever paired it — which strips the whole interaction
from every rebuilt transcript. Blocks are now retained per envelope id.
`lsp` is advertised as MCP again; the native `diagnostics` frame covers
one of ~10 actions.
(cherry picked from commit 4d269724a3a448886d13b4323ac02aadbfe38de3)
Address PR #6918 review:
- Gate escaping on the wire model id via isHarmonyDialectModel
(requestModelId ?? id), so deployment/catalog aliases whose local id
is opaque but whose wire id is gpt-5.x are still escaped.
- Escape client-boundary text of replayed native-history items
(user/developer/system message text and tool-result output) before
they hit the wire; the replay branch previously bypassed
convertResponsesInputContent entirely, sending stored markers raw.
Applies to both the shared Responses builder and the codex builder.
Assistant output, reasoning, and tool-call arguments stay untouched.
Fixes#6913
(cherry picked from commit 2c5cf286c447122fdb7633eaf3d9371370648966)
Harmony-server models (gpt-5.x / openai-codex) reject any Responses
request whose input reproduces reserved control-token spellings such as
`<|channel|>analysis`, failing before generation with invalid_prompt /
"Request blocked". The live agent-loop builders serialized user and
tool-result text verbatim into input_text / function_call_output, so a
tool result carrying the marker (e.g. a broad `omp://` grep) poisoned
the persisted session — every later turn replayed it and failed.
Escape reserved harmony tokens on the transport copy of untrusted
user/tool text in both the shared Responses builder and the codex
builder, gated on `preferredDialect === "harmony"`. The persisted
transcript is untouched and non-harmony models are unaffected. Extends
the existing compaction-payload escaping to the request boundary and
routes both through a shared `escapeHarmonyControlTokens` helper.
Fixes#6913
(cherry picked from commit dfea56588e7aa1c35971c4684221f4a86142e04e)
Reset text replay metadata whenever the projector enters a new source block, including unsigned terminal-only blocks.
Covered a signed terminal text block followed by an unsigned block to prevent invalid signature carryover.
Fixes#6703
Retained complete native web-search call/result pairs through leaked-thinking projection at their original source anchors.
Kept opaque server-tool blocks atomic during persistence, stripped them on reparent, and covered custom continuations plus compaction retention.
Fixes#6703
- Kept zero timeout resolutions distinct from missing first-event policy so the iterator does not fall back to the idle watchdog.
- Added deterministic coverage for an SSE response that opens before local prompt prefill emits its first event.
- Added a per-model first-event watchdog policy and disabled it for local OpenAI-compatible backends while retaining inter-event stall detection.
- Applied the policy to Responses and chat-completions transports with regression coverage for resolver and runtime precedence.
Fixes#6524
- Added handling for signature-bearing thinking blocks that never streamed deltas before closing.
- Implemented a safety net in `LeakedThinkingProjector` to recover thinking blocks with signatures missing per-block events.
- Added test coverage verifying proper projection order and recovery of unstreamed thinking signatures.
Deferred projected native thinking_end events until their source block ends so
stream consumers observe completed signatures before content_block_stop, even
when later projected blocks already started.
Covered the interleaved event lifecycle and Anthropic serialization path.
Fixes#6046
Mapped each native thinking content index to its projected block so a delayed
thinking_end updates the correct signature after text, tool calls, or another
thinking block has started.
Added regression coverage for two thinking blocks separated by a tool call and
finalized out of order.
Fixes#6046
wrapLeakedThinkingStream ignored thinking_end, where Anthropic surfaces the
completed thinkingSignature (delivered via signature_delta after every
thinking_delta). The projected native thinking block persisted an empty
signature, so continuations replayed signature:"" and Anthropic-compatible
signing endpoints rejected it.
Handle thinking_end in the wrapper and stamp the completed signature onto the
open projected thinking block via a new LeakedThinkingProjector.thinkingEnd.
Fixes#6046
Object-form additionalProperties/unevaluatedProperties are genuine
subschemas whose interior may still hold the bare booleans emitted by
toolWireSchema; skipping them let llama.cpp reach an Unrecognized
schema: true. Boolean forms stay intact for closed/open-object grammar
semantics. Flagged by Codex review on #5919.
Local grammar-constrained OpenAI-compatible servers (llama.cpp, LM Studio,
vLLM) build a GBNF grammar from tool JSON Schemas and 400 with
"Unrecognized schema: true" on a bare boolean subschema. The task tool's
open outputSchema field normalizes to boolean `true` (issue #1179), which
the converter cannot compile, breaking every tool request.
Add a "grammar" tool-schema flavor, auto-detected for local backends, that
widens bare boolean/empty subschemas into a value-accepting primitive union
while preserving closed-object `additionalProperties: false`.
Fixes#5914
Added dependencies, dependentSchemas, and dependentRequired to UNSUPPORTED_SCHEMA_FIELDS so both the Google and CCA normalizers drop them before serializing to the OpenAPI-style Schema wire, which cannot model draft-2019 conditional keywords.
Kept them in the MCP subschema-map path (still coercing boolean entries) and documented the additions in CONSTRAINTS.md.
Fixes#5604
Added not to the CCA residual incompatibility gate so false boolean subschemas fall back before reaching the legacy object-shaped parameters wire.
Made residual scanning schema-map aware to avoid treating a property literally named not as the unsupported keyword, and added root, property, and dependentSchemas regressions.
Fixes#5604
Generalized the schema-map walk context beyond properties to include patternProperties, dependencies, dependentSchemas, $defs, and definitions. Each arbitrary map entry is now treated as a subschema, so bare booleans coerce before reaching Google/CCA.
Reused the shared map/array keyword tables in the Ollama sanitizer and added a dependentSchemas regression for both Google and CCA normalizers.
Fixes#5604
Boolean JSON Schema subschemas (`true`/`false`, draft 6+) in MCP tool
inputs passed through normalizeSchemaForGoogle/normalizeSchemaForCCA
untouched. The Cloud Code Assist / Gemini protobuf Schema type has no
representation for a bare boolean, so requests bounced with a 400
INVALID_ARGUMENT before reaching the model.
Coerce booleans to their object equivalents (`true` -> `{}`,
`false` -> `{ not: {} }`) at the single normalizeSchemaNode choke point,
but only in genuine subschema slots (root, combiner branches,
subschema-valued keywords, property values). Keyword-slot booleans
(`nullable`, `enum` entries, `additionalProperties`) stay untouched so
Moonshot/MCP open-record markers survive.
Fixes#5604
Gave each Codex SSE transport attempt an independent pre-response watchdog while reserving the retry-loop signal for caller cancellation.
Added provider-level coverage for retry recovery and explicit abort behavior.
Fixes#5329
- Normalized completed image_generation_call results into assistant image blocks.
- Persisted image bytes through the session blob store and rendered them in live, replay, ACP, proxy, telemetry, and HTML paths.
- Added response normalization, persistence, and TUI rendering regressions.
Fixes#4768