AI Source Changes
July 22, 2026 · View on GitHub
2026-07-22 - Drop tool results of errored/aborted assistants in transformMessages
What changed and why
api/transform-messages.ts: the pairing pass now records the toolCall ids of every assistant it skips becausestopReason === "error" | "aborted"intodroppedCallIds(mirroring the existing skip condition), and the emit loop no longer emits a toolResult whosetoolCallIdis in that set — unless the id is also declared by a kept assistant (nextToolCallIndexById), which still pairs through the normal windows. Previously the errored assistant was dropped while its result (a real one, or a placeholder synthesized by the compaction pipeline'srepairOrphanedToolResults) survived, so the request carried arole:"tool"message whosetool_call_idno assistant declared; strict providers (apitopia/kimi openai-completions) reject it with400 tool_call_id ... is not found, permanently bricking compaction for the session. True orphans (id declared nowhere) and results of kept assistants are unchanged, and kept assistants' unanswered calls still get the synthetic "No result provided" result.utils/tool-pair-repair.ts:repairOrphanedToolResultsno longer synthesizes placeholder results for toolCalls declared by errored/aborted assistants (defense in depth; those assistants are dropped bytransformMessagesanyway). The coding-agent compaction copy received the identical guard; the two files remain verbatim copies.../test/transform-messages-errored-tool-results.test.ts: drop cases (errored + real result, aborted + synthesized placeholder), preservation cases (kept pair, "No result provided" synthesis, true orphan passthrough), and an id re-declared by a later kept assistant.../test/tool-pair-repair.test.ts: no synthesis for errored/aborted assistants, synthesis kept for a kept re-declaration.
Expected merge conflict zones
- LOW:
api/transform-messages.tssecond-pass pairing loop and toolResult emit branch;utils/tool-pair-repair.tsdangling-call synthesis loop.
2026-07-21 - OpenAI Responses provider-native completion reconciliation
What changed and why
api/openai-responses-shared.ts: opaque output items now occupy the existing output-index slot map, soresponse.output_item.donereplaces the partialaddedpayload with the final provider item. OpenAI web-search actions commonly arrive only on the done frame; retaining the added placeholder lost the final query/action before session persistence and app-server projection.../test/openai-responses.provider-native.test.ts: covers an action-less added web-search item followed by the completed done item.
Expected merge conflict zones
- LOW:
api/openai-responses-shared.tsoutput-slot creation andresponse.output_item.donefinalization.
2026-07-22 - Omit non-"fc" item ids when replaying tool calls as function_call
api/openai-responses-shared.tsconvertResponsesMessages(): afunction_callinput item'sidis now emitted only when it begins with "fc" — the Responses API rejects anything else (Invalid 'input[N].id': 'custom'. Expected an ID that begins with 'fc'.). Custom tool calls are stored with the<call_id>|customsentinel (acustom_tool_calloutput carries no server-issued item id), so replaying them without their freeform tool registered — compaction summarization stripsfreeformfrom its tool list — previously sentid: "custom"and hard-failed the whole request, tripping the compaction circuit breaker. Omitting mirrors the existing different-model pairing-validation skip; server-issuedfc_…ids still replay unchanged.../test/openai-responses-custom-tools.test.ts: sentinel omission plus a pin that genuinefcids survive same-model replay.
Expected merge conflict zones
- LOW:
convertResponsesMessagesfunction_call emission branch.
2026-07-20 - Typed classifier stop details
- Added optional typed refusal/sensitive stop details to assistant messages, preserving Anthropic classifier outcomes through streaming and faux provider errors.
- Exported
isClassifierRefusaland excluded classifier outcomes from generic same-model retry classification.
2026-07-20 - Live tool-result pairing by source position + Retry unsigned Anthropic thinking replay as text
What changed and why
Live tool-result pairing by source position
api/transform-messages.ts: live history normalization now indexes tool results and replayable tool calls by source position. Each tool call consumes the earliest still-unconsumed matching result after its declaring assistant, emits that result adjacent to the assistant turn, or emits exactly one synthetic error result. A repeated ID establishes a new pairing window, so a delayed result cannot attach to an earlier call or be replayed twice across an intervening user turn. Aborted and errored assistant turns remain excluded.../test/transform-messages-copilot-openai-to-anthropic.test.ts: covers delayed normalized results across a user turn, partial multi-call results, reused IDs with prior orphaned results, trailing unresolved calls, and Anthropic-required tool-result adjacency.
Retry unsigned Anthropic thinking replay as text
AnthropicMessagesCompat.unsignedThinkingReplaynow explicitly controls replay of thinking blocks without a usable signature. The safe default is text replay for first-party/signing endpoints; the legacyallowEmptySignatureflag remains an alias for Kimi-compatible empty-signature replay.- When an endpoint rejects an empty replay signature with a pre-stream HTTP 400 containing
Invalid signature in thinking block, the Anthropic adapter rebuilds the request with unsigned thinking demoted to text and retries exactly once. That learned fallback is scoped to the session, base URL, and model ID, without mutating sharedModelmetadata. - Signed and redacted thinking replay remains byte-for-byte/native-state preserving. Non-signature 400s and errors after SSE content begins do not retry.
Files modified
api/transform-messages.ts../test/transform-messages-copilot-openai-to-anthropic.test.tstypes.tsapi/anthropic-messages.ts../test/anthropic-unsigned-thinking-replay.test.ts
Expected merge conflict zones
- LOW:
api/transform-messages.tssecond-pass tool-result normalization. - LOW:
AnthropicMessagesCompatreplay options and Anthropic request creation.
2026-07-17 - Video input modality for Kimi K3 (kimi-coding)
What changed and why
types.ts:Model.inputunion gains"video". No new message content type: video payloads ride the existingImageContentblock with avideo/*mimeType (helperisVideoMimeType()exported) to keep the message contract and the upstream merge surface unchanged.api/transform-messages.ts:downgradeUnsupportedImagesnow first replaces video-mime blocks with a placeholder for models without the"video"modality (user and toolResult content), then applies the existing image downgrade. Prevents cross-model replay from sending video blocks to providers that reject them.api/anthropic-messages.ts:convertContentBlocksand the user-message block mapping serialize video-mime blocks as{type:"video", source:{type:"base64", media_type, data}}— the wire shape the Kimi Anthropic-compatible endpoint accepts (verified against MoonshotAI/kimi-code kosong anthropic provider). The block is not in the official SDK union, so it is cast like the existingtool_referenceescape hatch.scripts/generate-models.ts+ regeneratedproviders/kimi-coding.models.ts: kimi-codingk3declaresinput: ["text", "image", "video"].
Files modified
types.tsapi/transform-messages.tsapi/anthropic-messages.ts../scripts/generate-models.tsproviders/kimi-coding.models.ts(generated)../test/transform-messages-video.test.ts
Expected merge conflict zones
- LOW:
types.tsModel.inputunion andImageContentcomment. - MEDIUM:
api/anthropic-messages.tsconvertContentBlocks/convertToolResultif upstream reworks content serialization. - LOW:
api/transform-messages.tsdowngradeUnsupportedImages.
2026-07-19 - Name-preserving apply_patch replay characterization and policy coverage
What changed and why
- Added characterization + policy-table coverage for replaying mixed edit/apply_patch
history across every KnownApi: Responses targets serialize a historical apply_patch call
as
custom_tool_callwhen a freeform apply_patch is declared and asfunction_call(name preserved, JSON{input}args) otherwise; Completions/Anthropic/Google/Bedrock/ Mistral/pi-messages keep the stored name with native JSON-typed call entries. - No production change was required: existing converters already implement the name-preserving truth table. Tests pin both branches plus per-API shape assertions so a future regression cannot silently rename or drop historical patch calls.
2026-07-17 - Truncation-recovery contract for ToolCall and toolcall_end
What changed and why
- Truncated text-protocol tool calls were silently dropped, leaked as raw markup, or executed from a stale argument snapshot, with no public signal distinguishing a finalized (executable) call from one the parser could only partially recover. Consumers had no contract for "this tool call is incomplete; do not execute it; ask the model to retry."
ToolCallgains optionalincomplete?: trueanderrorMessage?: string, set by the text tool-call middleware when a truncated call could not be recovered. Carriers ofincompleteMUST NOT be executed; they are surfaced as a failed tool result so the model re-issues the call next turn.- The
toolcall_endmember ofAssistantMessageEventis redefined from an implicit "complete" to "finalized": atoolcall_endis executable iffincomplete !== true. Flagged ends still terminate the call (so the wrapper never holds a dangling partial) but are not executable. This is the release-note surface for the redefinition. ToolCallFormatgains"morph-xml"as the canonical id;"xml"is retained as a deprecated alias resolving to the same protocol, so existingmodels.jsonconfigs and compiled consumers ofgetProtocol("xml")keep working without a runtime normalization that rewrites stored config values.- Flagged dangling-call diagnostics always append
Re-issue the tool call with complete arguments.to parser-provided error messages without duplicating a final period. compat.tsnow publicly re-exportsgetToolCallFormat,getProtocol,transformContext, andwrapStreamWithToolCallMiddlewarefor composed providers that need the text tool-call middleware.
Files modified
types.ts(ToolCall,AssistantMessageEvent.toolcall_end,OpenAICompletionsCompat.toolCallFormatdoc)tool-call-middleware/types.ts,tool-call-middleware/index.ts,tool-call-middleware/context-transformer.ts../test/tool-call-middleware/context-transformer.test.ts,../test/tool-call-middleware/stream-integration.test.ts
Why the higher-level extension system couldn't handle this alone
- The canonical
ToolCallshape, thetoolcall_endevent contract, and theToolCallFormatunion are all exported frompi-aiand consumed by standalonepi-aiclients before any coding-agent extension runs.
Expected merge conflict zones
- LOW:
types.tsaround theToolCallandAssistantMessageEventdeclarations. - LOW:
tool-call-middleware/types.tsToolCallFormatunion andtoolcall_endvariant.
2026-07-17 - Moonshot root object-union compatibility
What changed and why
utils/tool-schema-compat.ts: Moonshot normalization now flattens a rootanyOf/oneOfof object parameter shapes into onetype: "object"schema. Properties are merged and only branch-common required fields remain. Kimi rejects a root combiner withouttype, but also rejects a sibling roottypebeside that combiner, so the union must be represented as a permissive object at the function-parameter boundary.../test/openai-completions-tool-schema-compat.test.ts: covers the realclick-style coordinate/index union and the final post-hook request payload.
Why the higher-level extension system couldn't handle this alone
- The provider adapter owns the final wire schema after payload hooks and is the only layer shared by direct Moonshot requests and custom Moonshot-compatible gateways.
Expected merge conflict zones
- LOW:
utils/tool-schema-compat.tsif upstream expands its provider-specific schema normalizers.
2026-07-17 - Final-boundary Moonshot tool schema normalization
What changed and why
api/openai-completions.ts: re-normalizes function tool parameter schemas afteronPayloadand immediately before the OpenAI SDK request. Payload hooks can replace or inject tools after the ordinaryconvertToolspass; those tools previously bypassed the Moonshot/MFJS compatibility transform and could retain a parenttypebesideanyOf, which Moonshot rejects with HTTP 400.../test/openai-completions-tool-schema-compat.test.ts: captures the real HTTP request and locks the post-hook wire shape.
Why the higher-level extension system couldn't handle this alone
before_provider_requestis exposed throughonPayload, so the provider adapter is the only layer that can validate the complete tool list after every hook has run.
Expected merge conflict zones
- LOW:
api/openai-completions.tsaround theonPayloadcallback and final request submission.
2026-07-16 - Anthropic native web_search endpoint guard and server_tool_use input streaming
What changed and why
types.ts: addedAnthropicMessagesCompat.supportsWebSearch. Default (resolved ingetAnthropicCompat): true only for the first-partyapi.anthropic.comendpoint; compatible providers and provider overrides can opt in per model viacompat.api/anthropic-messages.ts:sanitizeUnsupportedNativeToolsnow also strips hook-injected nativeweb_search_*tools when the resolved compat does not support them, mirroring the existing native computer tool guard and the OpenAI Responsesweb_search_previewcompat guard (2026-05-15). Anthropic-compatible endpoints such as kimi-coding execute the server-side search but reject the replayedserver_tool_use/web_search_tool_resultblocks on the next request (kimi-coding 400s withtool_call_id is not found), wedging the session. Namedtool_choiceis preserved when a same-name function fallback remains and removed only when the retained tool list no longer contains that choice.api/anthropic-messages.ts: same-model provider-native replay also drops web-search server-tool blocks (server_tool_usenamedweb_searchandweb_search_tool_result) when the endpoint lackssupportsWebSearch. Sessions that already recorded such blocks against an incompatible endpoint were permanently wedged — every request replayed the rejected blocks; dropping the pair loses the searched context but unwedges the session.api/anthropic-messages.ts: streaming now accumulatesinput_json_deltafor Anthropic's confirmed provider-native tool-use blocks (server_tool_useand betamcp_tool_use) and merges the parsed input into the stored raw block atcontent_block_stop(or in the abort/error finalizer for interrupted streams). Previously the block kept thecontent_block_startsnapshot (input: {}), so every same-model replay sent the server tool call with an empty input. Unknown and result-shaped blocks are never touched; their raw provider payload must remain verbatim.
Files modified
types.tsapi/anthropic-messages.ts../test/anthropic-native-web-search-compat.test.ts../test/anthropic-provider-native-replay.test.ts../test/anthropic-web-search-replay-encryption.test.ts../test/anthropic.provider-native.test.ts- (see also
../../coding-agent/src/core/changes.mdfor the models.json compat schema entry)
Why the higher-level extension system couldn't handle this alone
- Extensions can inject native
web_search_*tools viabefore_provider_request; the final payload is only known after all hooks run, so the provider is the last reliable guard before SDK submission (same rationale as the OpenAI Responses guard). Provider-native block capture during streaming happens insidepi-aibefore any extension sees the message.
Expected merge conflict zones
- MEDIUM:
api/anthropic-messages.tsaroundgetAnthropicCompat,sanitizeUnsupportedNativeTools, and thecontent_block_delta/content_block_stopstreaming handlers. - LOW:
types.tsAnthropicMessagesCompatif upstream adds more compat flags.
2026-07-14 - Anthropic web search replay encrypted content correction
What changed and why
api/anthropic-messages.ts: same-model provider-native replay now preserves each nestedweb_search_resultitem'sencrypted_contentbyte-for-byte before sending prior server-side web search results back in the next Anthropic request. The existing same-provider/api/model boundary, fallback pruning, and cross-model dropping behavior remain unchanged.- Anthropic's current web-search contract requires
encrypted_contentto be passed back unmodified for multi-turn use. The July 8 stripping workaround was wrong under that contract: it discarded opaque provider-owned replay state after one observed 400, even though the raw session stored all seven encrypted fields and Senpi removed them during conversion.
Files modified
api/anthropic-messages.ts../test/anthropic-provider-native-replay.test.ts../test/anthropic-web-search-replay-encryption.test.ts
Expected merge conflict zones
- LOW:
api/anthropic-messages.tsaroundsanitizeReplayableAnthropicProviderNativeBlockand the provider-native replay path.
2026-07-06 - Anthropic server-side fallback replay contract
What changed and why
- The server-side fallback beta (
server-side-fallback-2026-06-01) emits afallbackcontent block mid-response when the serving model falls back (e.g. aclaude-fable-5refusal replaced by the fallback model). Three fixes (2026-07-02 → 2026-07-06) make replaying such turns conform to the beta's contract:fallbackwas added toREPLAYABLE_ANTHROPIC_PROVIDER_NATIVE_TYPES; dropping it on same-model replay mutated the latest assistant message's block sequence and the API rejected the next request of the turn with a 400thinking … cannot be modifiederror, wedging the session.- Blocks emitted before the final
fallbackmarker belong to the discarded attempt and are now omitted on replay; replaying them verbatim left pre-boundarytool_useblocks without matchingtool_results, rejected with 400tool_use ids were found without tool_result blocks. - An unpaired pre-boundary
server_tool_use(fallback interrupted the declined attempt before the server tool's result arrived) is also dropped; paired server-tool blocks and text still replay verbatim.
Files modified
api/anthropic-messages.tstest/anthropic-provider-native-replay.test.ts
Why the higher-level extension system couldn't handle this alone
- Provider-native block replay filtering happens inside the Anthropic message transformer before any coding-agent extension can rewrite provider payloads.
Expected merge conflict zones
- MEDIUM:
api/anthropic-messages.tsaroundREPLAYABLE_ANTHROPIC_PROVIDER_NATIVE_TYPESand the assistant-turn replay/filter path. - LOW:
test/anthropic-provider-native-replay.test.tsfixtures if upstream restructures replay tests.
2026-07-02 - Upstream provider metadata and Codex SSE transport sync
What changed and why
api/openai-codex-responses.ts: accepted upstream zstd request-body compression for Codex Responses SSE while preserving the fork's senpi-branded Codex headers, stale response handling, service-tier support, and thinking support.utils/oauth/device-code.tsandutils/oauth/github-copilot.ts: accepted delayed GitHub Copilot device-code polling and related OAuth cleanup.- Provider model catalogs were refreshed for Copilot, Fireworks, OpenCode, Cloudflare AI Gateway, Bedrock, and related
providers while retaining fork-specific model capability metadata such as
supportsXhigh.
Files modified
api/openai-codex-responses.tsproviders/amazon-bedrock.models.tsproviders/cloudflare-ai-gateway.models.tsproviders/fireworks.models.tsproviders/github-copilot.models.tsproviders/opencode-go.models.tsproviders/opencode.models.tsutils/oauth/device-code.tsutils/oauth/github-copilot.ts
Why the higher-level extension system couldn't handle this alone
- Codex SSE request compression, OAuth polling, and generated provider metadata all live inside
pi-aibefore coding-agent extensions can intercept a request or model catalog entry.
Expected merge conflict zones
- MEDIUM:
api/openai-codex-responses.tsaround request body creation, zstd encoding, headers, and stream response handling. - LOW:
utils/oauth/device-code.tsaround polling cadence and error handling. - LOW: provider
*.models.tscatalogs when upstream regenerates model metadata.
2026-05-19 - Cloudflare Anthropic computer tool guard
What changed and why
providers/anthropic.ts: Cloudflare Anthropic routes now strip hook-injected nativecomputer_*tools afteronPayload, while preserving supported native tools such asbash_20250124andtext_editor_20250124.- Computer-use beta request headers are removed only for routes/models that reject the native computer tool.
- Added a regression matching the CF runtime error where
computer_20250124is not one of the accepted tool tags.
Files modified
providers/anthropic.ts../test/anthropic-on-payload-headers.test.ts
Why the higher-level extension system couldn't handle this alone
- The failing payload can be introduced by
before_provider_request; the provider adapter is the final point that sees the complete Anthropic request before SDK submission.
Expected merge conflict zones
- LOW: native-tool sanitization helpers near request metadata extraction.
2026-05-18 - Anthropic protected thinking replay
What changed and why
providers/anthropic.ts: signed Anthropicthinkingreplay now forwards the stored text exactly as-is instead of running it through local surrogate sanitization. Anthropic treats signed and redacted thinking blocks as protected replay state; rewriting them can make the next tool-result request fail withthinking/redacted_thinkingmodification errors.providers/transform-messages.ts: same-model preserved provider-state blocks are now copied rather than shared, and redacted thinking remains same-model only. Cross-model transforms still drop opaque redacted thinking state.- Added regressions for signed thinking replay, redacted thinking replay, immutable same-model transforms, cross-model redacted thinking dropping, and retry context behavior after a failed assistant turn.
Files modified
providers/anthropic.tsproviders/transform-messages.ts../test/anthropic-thinking-disable.test.ts../test/transform-messages-copilot-openai-to-anthropic.test.ts../../coding-agent/test/suite/regressions/0000-anthropic-partial-thinking-replay.test.ts
Why the higher-level extension system couldn't handle this alone
- Anthropic protected thinking is serialized inside
pi-ai's provider adapter after history transformation. Extensions and coding-agent retry logic cannot safely repair a signed block once the provider has normalized or shared it.
Expected merge conflict zones
- LOW:
convertMessages()signed/redacted thinking block serialization inproviders/anthropic.ts. - LOW: same-model
preserveProviderStatebranches inproviders/transform-messages.ts.
2026-05-15 - OpenAI Responses web_search_preview compat guard
What changed and why
providers/openai-responses.ts: afteronPayloadhooks run, custom OpenAI Responses endpoints now strip nativeweb_search_preview/web_search_preview_2025_03_11tools, the matchingtool_choice, andweb_search_call.action.sourcesincludes unlesscompat.supportsWebSearchPreviewexplicitly opts in. Officialapi.openai.comendpoints keep the existing default support.types.ts: addedOpenAIResponsesCompat.supportsWebSearchPreviewso custom providers can declare support when they really pass OpenAI-native Responses tools through.- Added regression coverage for hook-injected native web search on a custom Responses endpoint and the explicit opt-in path.
Files modified
providers/openai-responses.tstypes.ts../test/openai-responses-web-search-compat.test.ts
Why the higher-level extension system couldn't handle this alone
- External or user extensions can add provider-native tools through
before_provider_request; the final OpenAI Responses payload is only known after all hooks have run. The provider is the last reliable guard before SDK submission.
Expected merge conflict zones
- LOW:
streamOpenAIResponses()request construction immediately after theonPayloadcallback. - LOW:
OpenAIResponsesCompatif upstream adds more Responses compatibility flags.
2026-05-15 - Opus 4.6/4.7 unsupported native computer tool guard
What changed and why
providers/anthropic.ts: afteronPayloadhooks run, Opus 4.6 and 4.7 requests now strip Anthropic's legacy nativecomputer_20250124tool and removecomputer-use-2025-01-24from hook-addedanthropic-betarequest headers.- Added a regression to cover extension-style payload mutation where a native computer tool is injected alongside another supported native tool. The supported tool and remaining beta header survive; the Opus-rejected computer tool does not reach the SDK request body.
Files modified
providers/anthropic.ts../test/anthropic-on-payload-headers.test.ts
Why the higher-level extension system couldn't handle this alone
- External or user extensions can add provider-native tools through
before_provider_request; the final provider payload is only known after all hooks have run. The Anthropic provider is the last reliable guard before SDK submission.
Expected merge conflict zones
- LOW:
streamAnthropic()request construction immediately after theonPayloadcallback. - LOW: native-tool sanitization helpers near request metadata extraction.
2026-05-15 - Anthropic onPayload request headers
What changed and why
providers/anthropic.ts: when anonPayloadhook returns request metadata fields (headers/extra_body), the provider now forwards string-valuedheadersthrough the Anthropic SDK request options and strips both metadata keys from the JSON request body.- Added a regression test for native computer-use extensions that inject
computer_20250124plusanthropic-beta: computer-use-2025-01-24frombefore_provider_request. Previously the tool reached Anthropic but the beta header did not, producing a 400 wherecomputer_20250124was not among the accepted tool tags.
Files modified
providers/anthropic.ts../test/anthropic-on-payload-headers.test.ts
Why the higher-level extension system couldn't handle this alone
- Extensions can mutate the provider payload via
before_provider_request, but Anthropic SDK request headers are assembled insidepi-ai. The provider must explicitly lift hook-added header metadata into SDK request options afteronPayloadruns.
Expected merge conflict zones
- LOW:
streamAnthropic()request construction around theonPayloadcallback and SDKmessages.create()options.
2026-05-11 - Senpi-branded Codex originator and User-Agent
What changed and why
providers/openai-codex-responses.tsbuildBaseCodexHeaders(): changed the hardcodedoriginator: "pi"and theUser-Agent: "pi (…)"string to"senpi". Upstream chose"pi"as the Codex CLI identity; this fork's identity issenpi.utils/oauth/openai-codex.tscreateAuthorizationFlow(): changed the defaultoriginatorparameter from"pi"to"senpi"and updated the JSDoc onloginOpenAICodexaccordingly. Callers can still pass their own originator.
Files modified
providers/openai-codex-responses.tsutils/oauth/openai-codex.ts
Why the higher-level extension system couldn't handle this alone
- The originator + User-Agent headers are built inside
pi-ai's Codex header constructor before the request leaves the library. Coding-agent extensions cannot intercept the header construction step.
Expected merge conflict zones
- LOW:
buildBaseCodexHeaders()body (3 lines) and theoriginatordefault parameter / JSDoc increateAuthorizationFlow.
2026-05-07 - Shared tool pair repair utility for compaction-safe histories
What changed and why
- Added
utils/tool-pair-repair.tsto centralize bidirectionaltool_use/tool_resultpairing repair inpi-ai. - This supports both coding-agent builtin extensions and external
pi-aiconsumers that do not load coding-agent extensions.
Files modified
utils/tool-pair-repair.ts
Why the higher-level extension system couldn't handle this alone
- Extension code alone is not available to standalone
pi-aiconsumers, so this shared history repair logic must live inpi-ai.
Expected merge conflict zones
- None expected; this is a new additive utility file.
2026-04-13 - OpenAI Responses custom tool support for apply_patch
What changed and why
- Added optional freeform grammar metadata to tool types.
- Updated OpenAI Responses request/history conversion to emit and preserve
custom/custom_tool_call/custom_tool_call_outputitems for freeform tools. This was required to match Codex GPTapply_patchbehavior instead of falling back to JSON function tools.
Files modified
types.tsproviders/openai-responses-shared.ts
Why the higher-level extension system couldn't handle this alone
pi-aionly serialized tools as JSON function definitions for OpenAI Responses, so a builtin extension could not produce Codex-compatible freeform tools without core provider changes.
Expected merge conflict zones
types.tstool modelproviders/openai-responses-shared.tsrequest/stream conversion paths
2026-04-17 - Claude Opus 4.7, max effort alignment, and extra-body pass-through
What changed and why
- Added
claude-opus-4-7to the Anthropic provider and its Bedrock cross-region profiles (anthropic.*,us.*,eu.*,global.*) so Opus 4.7 is available in the catalog and survives re-runs ofgenerate-models.ts. - Expanded
supportsXhigh()to includeopus-4-7/opus-4.7so the coding agent exposesxhighfor Opus 4.7 users. - Expanded Anthropic adaptive thinking support (
supportsAdaptiveThinking) and effort mapping (mapThinkingLevelToEffort) for Opus 4.7:xhighnow maps to the native"xhigh"effort on Opus 4.7 (Anthropic's newest tier).xhighstill maps to"max"on Opus 4.6 (Opus 4.6 doesn't support nativexhigh).- Added explicit
"max"to the effort type union for future use. - Cast through
{ output_config?: { effort: AnthropicEffort } }while the @anthropic-ai/sdk upstream types still reject"xhigh".
- Added
StreamOptions.extraBodyfor pass-through custom body fields (matches opencode's provideroptions). Wired it through every builtin provider's payload builder (anthropic,openai-responses,openai-completions,azure-openai-responses,openai-codex-responses,mistral,google,google-vertex,google-gemini-cli,amazon-bedrock). A sharedapplyExtraBodyhelper and per-provider reserved-key sets live inproviders/simple-options.tsto prevent users from overriding provider-managed fields (model id, messages, stream flag, etc.).
Files modified
types.tsmodels.tsmodels.generated.tsproviders/simple-options.tsproviders/anthropic.tsproviders/openai-responses.tsproviders/openai-completions.tsproviders/azure-openai-responses.tsproviders/openai-codex-responses.tsproviders/mistral.tsproviders/google.tsproviders/google-vertex.tsproviders/google-gemini-cli.tsproviders/amazon-bedrock.tsscripts/generate-models.ts
Why the higher-level extension system couldn't handle this alone
- Extra-body pass-through has to be read inside each provider's payload builder (pre-
onPayloadhook), which is corepi-aiterritory; a coding-agent extension cannot reach intopi-aiprovider payload construction. - Opus 4.7 model metadata, xhigh capability detection, and adaptive thinking effort mapping all live in
pi-ai.supportsXhigh,supportsAdaptiveThinking, andmapThinkingLevelToEffortare internal to the provider. - Running
generate-models.tsregeneratesmodels.generated.tsfrom models.dev; the Opus 4.7 override block ensures the upstream regeneration keeps our entry.
Expected merge conflict zones
scripts/generate-models.tsOpus override block (lines around the 4.6 additions).src/providers/anthropic.tssupportsAdaptiveThinking/mapThinkingLevelToEffort/AnthropicEffort.src/providers/simple-options.ts(new exports).src/models.tssupportsXhigh.src/types.tsStreamOptions.extraBody.
2026-04-17 (follow-up) - "max" ThinkingLevel + tightened extraBody guards + Google config merge
What changed and why
- Exposed Anthropic's native
"max"effort through the unifiedThinkingLevelsurface:StreamOptions.reasoning: "max"maps tomaxon Opus 4.6/4.7, clamps tohighon other adaptive models, and falls back to thehighbudget on budget-based Anthropic models. OpenAI-style providers clampmaxtoxhighon xhigh-capable models (GPT-5.2/5.3/5.4) and tohighotherwise via a newclampMaxForOpenAIhelper. - Extended the per-provider reserved-key sets so
extraBodycannot stomp library-managed fields. New reservations includemetadata,temperature,store,stream_options,provider,providerOptions,tool_stream,prompt_cache_key,prompt_cache_retention,service_tier,promptMode,requestMetadata. The Google reserved set now targets the innerconfigobject (which the @google/genai SDK serializes as the HTTP request body) withsystemInstruction/tools/toolConfig/generationConfig/thinkingConfig/responseMimeType/responseSchema/cachedContent/abortSignal/httpOptionsreserved. - Merged Google and Google Vertex
extraBodyintoparams.configinstead of the top-levelGenerateContentParametersso user-supplied fields actually reach the Gemini wire (the SDK does not serialize root-level unknown fields). - Updated
adjustMaxTokensForThinking/clampReasoningto accept the new"max"level without crashing on missing budget entries.
Files modified (follow-up)
src/types.ts(ThinkingLevel adds"max")src/providers/simple-options.ts(addedclampMaxForOpenAI, tightened reserved sets, Google reservations targetconfig)src/providers/anthropic.ts(mapThinkingLevelToEffortnativemaxcase, JSDoc refresh, reserved keysmetadata+temperature)src/providers/openai-responses.ts,openai-completions.ts,openai-codex-responses.ts,azure-openai-responses.ts(useclampMaxForOpenAIon xhigh-capable models)src/providers/amazon-bedrock.ts(budget table addsmax, clampmaxon budget-based path)src/providers/google.ts,google-vertex.ts(merge extraBody intoconfig)
Why the higher-level extension system couldn't handle this alone
- The
ThinkingLevelunion, provider effort mapping, and reserved-key sets all live insidepi-ai. Exposing"max"to the coding agent requires widening the shared union and updating every provider's payload builder and option-derivation logic.
Expected merge conflict zones (follow-up)
src/types.tsThinkingLevelunion.- Each provider's
streamSimple<Provider>reasoning mapping block. src/providers/simple-options.tsexported reserved-key sets.
2026-07-22 - Thinking content stream timing metadata
What changed and why
ThinkingContentnow exposes optionalstartedAtandendedAtepoch-millisecond fields. The agent loop stamps these at provider stream-event receipt on a best-effort basis, allowing consumers to measure individual reasoning-block duration without changing provider event contracts.
Expected merge conflict zones
- LOW:
src/types.tsThinkingContentinterface.