Commit graph

1272 commits

Author SHA1 Message Date
David Brailovsky
3bc6eebacf fix(ai): hold Anthropic system updates past dropped assistant messages
A mid-conversation system message was placed before checking whether the
next assistant message converts to any blocks. When it did not (empty text,
or another model's reasoning-only reply), the update landed before a user
message and Claude rejected the request with a 400.

closes #11091
2026-10-22 12:43:42 +02:00
Vegard Stikbakke
cae8d69e57
fix(ai): use OpenRouter-reported total cost (#10286)
Use authoritative OpenRouter billing for Chat Completions and Anthropic Messages while retaining catalog component estimates and fallback behavior.

Fixes #9980
2026-10-22 11:01:39 +02:00
Vegard Stikbakke
7c22a93d0e
feat(ai): add totalFromProvider flag to usage cost (#11178)
Add Usage.cost.totalFromProvider to record whether cost.total is the
billed cost reported by the provider or an estimate from catalog rates.
Catalog-derived totals set it to false; a missing value also means
false. combineUsage marks a sum as provider-reported only when both
parts are.
2026-10-22 09:02:01 +02:00
Armin Ronacher
4a080252f0 Release v1.4.0 2026-10-22 00:29:13 +02:00
Armin Ronacher
7f225f25d8 feat(ai,coding-agent): image input for System One classifiers
Jev (TypeSafe, Workers AI), Clef (Workers AI), and OpenRouter decision
models now accept ClassifierContext.images. Each service takes its own
wire form: TypeSafe assets with the opt-in header, Clef images data URLs,
or OpenRouter image_url parts in the state. A new SystemOneCompat
(imageInput, sendImageOptInHeader) selects the form; catalog entries set
it explicitly and models.json provider compat now applies to classifiers.

Adds Clef Omni and Jev 1.14 on Workers AI and fixes Clef Flash and
Workers AI Jev catalog pricing.
2026-10-21 23:53:47 +02:00
Mario Zechner
04a93bad8f fix(ai,agent,coding-agent,durable): client identity follow-ups
- ClientAttribution is { name, url, categories? } with required name and url, so
  OpenRouter always gets an HTTP-Referer. identity.ts maps the fields to each
  provider's headers; NVIDIA gets the name with its first letter upper-cased.
  attribution: null turns it off. PI_ATTRIBUTION is pi's, including the
  cli-agent category; agent-core, durable, and the coding agent use it.
- Categories are a string array, joined for OpenRouter.
- withClientIdentity() is a typed wrapper instead of a Proxy and keeps a
  MutableModels mutable. Durable no longer caches wrappers in a WeakMap.
- The Codex originator stays pi; login agentName no longer follows attribution.
- Models and the coding agent's ModelRuntime add x-opencode-session for custom
  providers pointing at OpenCode, replacing the coding agent's transformHeaders.
2026-10-21 11:18:16 +00:00
Armin Ronacher
adc5fa9f80 feat(ai,agent,coding-agent): move client identity and provider attribution into pi-ai
pi-ai gets a ClientIdentity request option and createModels() default that choose the User-Agent (detailed with version, short, or custom) and provider attribution headers for OpenRouter, NVIDIA NIM, and OpenCode. Attribution is off by default in pi-ai and on by default in pi-agent-core and pi-durable (HarnessOptions.identity). pi-coding-agent sets its identity from the telemetry setting instead of adding headers itself.
2026-10-21 11:17:02 +00:00
David Brailovsky
6e491cfa95 fix(ai): match retryable status codes only as standalone numbers
Status codes inside request ids or other numbers no longer make an error retryable.

closes #11057
2026-10-21 11:59:38 +02:00
David Brailovsky
29057ece3e fix(ai): retry Codex CLI stream disconnect errors relayed by proxies
closes #11057
2026-10-21 11:54:47 +02:00
David Brailovsky
cedefc5326 fix(ai): handle ChatGPT Business workspace usage limit errors
Sign in with ChatGPT on a ChatGPT Business plan can fail with
subscription_sharing_workplace_individual_usage_limit_exceeded. Only the
workspace admin can raise this limit, so retrying never helps.

- Mark the error as non-retryable
- Add a hint to the error message telling the user to ask their
  workspace admin to increase the limit
2026-10-21 10:11:54 +02:00
Armin Ronacher
530015d93a fix(ai): only list announced removals as still unavailable
The incremental tools notice precedes the removal notice of the same message, so it now restates only removals announced by earlier messages. resolveTranscriptTools and hasNonAdditiveToolChanges share one scan for the last non-additive tool change.
2026-10-20 09:55:17 +02:00
Armin Ronacher
caa6967b62
fix(ai): keep the prompt cache across tool changes on Responses (#11142)
Models with additional_tools declare every tool in additional_tools items, starting with one at the head of the input, like Codex. A later item replaces same-name definitions from earlier items but cannot replace a top-level tool or remove a tool, so removals are announced as text and later updates restate the tools that are still unavailable.

Tool search and Kimi tool messages move the top-level tool list once per removal or redeclared name and keep later loads anchored, instead of resending the current tool list on every load after the first removal.

fixes #10940
2026-10-20 09:54:23 +02:00
Petr Houška
73f8de80bc fix(ai): recover from Copilot request-size overflow
Copilot rejects request bodies over its byte-size limit with HTTP 413 and a generic
'failed to parse request' body, so the existing token-count heuristics never matched.
Treat any HTTP 413 as request-size overflow regardless of provider or body text, so
the agent compacts and retries instead of surfacing a raw API error.

Validated against the live Copilot API (40MB image payload): main classifies the 413
as a plain error, this branch detects it as overflow.
2026-10-19 22:46:26 +02:00
Mario Zechner
2ec55d808d feat: Durable and pi-ai telemetry; Context.telemetry is always a span
- pi-ai: AI_TELEMETRY_SCHEMA; pi.ai.request and pi.ai.poll spans around Models requests via a lazyStream observer
  (no provider changes), first_chunk and block events (never per delta), usage/cost end attributes, provider_error
  status with a bounded message, aborted stays ok; duration, first-chunk, token and cost metrics; option renamed
  telemetryContext -> telemetry (a parent span)
- pi-telemetry: TelemetryContext carries readonly metrics and propagation capabilities on every span
- chord/context: Context.telemetry is always a TelemetrySpan (NOOP default); withTelemetry takes spans
- durable: DURABLE_TELEMETRY_SCHEMA; pi.durable.submit (inputs start their own trace linked to the caller) and
  pi.durable.phase spans for every task phase; stored carriers on submissions and tasks (TaskOptions.trace, links);
  entry, submission, checkpoint, replay and report events after commit; resumed tasks stay in their trace; phase
  duration, active task and report metrics; failed phases carry fixed categories with bounded messages
2026-10-19 06:26:02 +00:00
Christian Klotz
8efd314fa3
docs(ai,coding-agent): generate models.json reference from schemas (#11038)
* docs(ai,coding-agent): generate models.json reference from schemas

Add npm run generate:references, which renders docs/models-configuration.md
from the models.json TypeBox schemas, and describe provider and model fields
in those schemas. Overrides get their own descriptions without defaults, and
compat sections cover the Responses API aliases.

closes #10790

* Modify changelog

* docs(ai,coding-agent): use shared thinkingLevelMap and promptCache schemas

Replace the per-use schema helpers with fixed schemas whose descriptions
hold for both models and modelOverrides entries.
2026-10-16 21:53:00 +01:00
David Brailovsky
698a2d23b8 fix(ai): retry more gateway upstream failure errors
Adds upstream_error, upstream_stream_read_error, interrupted stream, and
HTTP/2 stream failure wording to the retryable error patterns.

closes #10920
2026-10-16 17:12:24 +02:00
David Brailovsky
4dd2b149d1 fix(ai): treat whitespace-only tool output as empty
Whitespace-only tool results (e.g. a bare \r\n from bash) were sent verbatim
to OpenAI Completions, OpenAI Responses, and Google providers. Some providers
reject that with a 400, which then repeats on every request in the session.
They now use the same placeholder as empty output.

closes #8720
2026-10-16 14:06:06 +02:00
David Brailovsky
0fbdbd3f8a fix(ai): retry node-fetch 'request to ... failed' errors
Google Vertex ADC token refresh failures are reported by node-fetch as
'request to <url> failed, reason: ...' and were never auto-retried.

fixes #10996
2026-10-16 10:48:22 +02:00
David Brailovsky
9113094291
fix(ai): drop thinking from other models instead of sending it as text (#11000)
When switching models, thinking blocks from earlier assistant messages
produced by a different model were sent as plain text. The new model saw
the old model's reasoning as part of its replies and could start writing
its own reasoning into the visible answer. These blocks are now dropped;
text and tool calls are kept.

closes #10995
2026-10-15 19:38:33 +02:00
Armin Ronacher
5ab7fe74c4 fix(ai): keep images of error tool results for Anthropic and Bedrock Claude
Anthropic and Claude on Bedrock reject error tool results that contain
images. Instead of replacing the images with a note, each image in the
result is now replaced with a placeholder and the images are sent after
all tool results of the turn, in the same user message, under a caption
naming the tool call. Successful results, error results without images,
and non-Claude Bedrock models are unchanged.

closes #10794
2026-10-15 16:49:50 +02:00
David Brailovsky
a3f265b1c6 feat(ai): use Chat Completions for the kimi-coding provider
Switch kimi-coding to OpenAI Chat Completions at api.kimi.com/coding/v1,
the protocol the official Kimi Code client uses. K3, K3-256K, and Kimi For
Coding accept tools declared in later system messages, so tools loaded
mid-conversation (e.g. by tool_search) keep the prompt cache. Highspeed
rejects tool-bearing system messages and keeps added tools top-level.

Re-lands b06c1f2 (reverted in 6a1f799b6) with MINIMUM_PI_VERSION bumped
to 1.2.1. Released clients implement only Anthropic Messages for
kimi-coding and ignore a model's api, so they must keep receiving the
last catalog published under 0.80.7.

closes #10720
2026-10-15 15:29:11 +02:00
David Brailovsky
6a1f799b67 fix(ai): revert kimi-coding switch to Chat Completions
Reverts b06c1f2. The pi.dev model catalog is served to every client
since 0.80.7, so released clients received kimi-coding models with
api openai-completions and baseUrl https://api.kimi.com/coding/v1.
Their kimi-coding provider only implements Anthropic Messages and
ignores model.api, so requests went to /coding/v1/v1/messages and
failed with 404. The change will re-land together with a bump of the
catalog's minimum pi version.

refs #10720, refs #10985
2026-10-15 13:19:40 +02:00
David Brailovsky
dc939913f6 fix(ai): support off thinking level on Claude Haiku 5.5 and Opus 5
Both models accept thinking: {type: "disabled"}, but were treated like
Opus 5.5, which cannot turn thinking off. "off" now sends disabled
thinking without effort markers or block_binding, which Anthropic
rejects while thinking is disabled.

fixes #10753
2026-10-15 11:24:04 +02:00
David Brailovsky
550f1f3ecd fix(ai): retry "The service is temporarily unavailable" errors
closes #10965
2026-10-15 10:12:50 +02:00
David Brailovsky
b06c1f2cf1 feat(ai): use Chat Completions for the kimi-coding provider
Switch kimi-coding to OpenAI Chat Completions at api.kimi.com/coding/v1,
the protocol the official Kimi Code client uses. K3, K3-256K, and Kimi For
Coding accept tools declared in later system messages, so tools loaded
mid-conversation (e.g. by tool_search) keep the prompt cache. Highspeed
rejects tool-bearing system messages and keeps added tools top-level.

closes #10720
2026-10-14 22:23:59 +02:00
David Brailovsky
87f9a36419
fix(ai): report malformed final tool-call arguments instead of keeping the parsed prefix (#10944)
When a finished tool call's arguments text was not valid JSON, the
streaming parser kept only the fields before the error and the tool ran
with them. Finished calls are now parsed strictly (with the existing
string repair); on failure arguments are {} and argumentsError holds the
reason and raw text, which validateToolArguments reports to the model.

fixes #10897
2026-10-14 21:41:42 +02:00
David Brailovsky
b39979799f feat(ai): remove unused assistant-message frame API
AssistantMessageFrameEncoder and reduceAssistantMessageFrames were only used by the removed experimental agent harness.
2026-10-14 14:37:14 +02:00
David Brailovsky
1692bba634 fix(ai): send thinkingLevel for Gemini 4 models
Gemini 4 Argon rejected thinkingBudget on Vertex and ignored the selected
thinking level on the Gemini API.

fixes #10890
2026-10-14 12:38:28 +02:00
David Brailovsky
63035734f2 fix(ai): order thinking before text when a completions chunk has both
Proxies such as LiteLLM can send reasoning_content and content in one
stream delta. Reasoning was handled after content, so the message ended
up as [text, thinking]. Handle reasoning fields and reasoning_details
before content and tool calls.

fixes #10926
2026-10-14 12:11:15 +02:00
David Brailovsky
7efebff189 fix(ai): retry gateway 'Unable to forward upstream stream' errors
closes #10920
2026-10-14 11:14:38 +02:00
David Brailovsky
60ac3bcc3b
feat(ai): keep the prompt cache when changing thinking level on OpenAI GPT-6 (#10930)
With an API key, GPT-6 models on the openai provider now keep reasoning.effort
fixed and send the thinking level as configuration_update input items. Each
reply records its level, so earlier items are rebuilt unchanged on every request.
Controlled by compat.supportsMidConvoEffort; skipped for Sign in with ChatGPT
and thinking off.

closes #10853
2026-10-14 10:56:20 +02:00
David Brailovsky
195120a915 fix(ai): move OpenCode providers to inference endpoints
- Use opencode.ai/inference and opencode.ai/inference/go base URLs; /zen/go has no Gemini route (404)
- Add google-generative-ai support to the OpenCode Go provider
- Use Anthropic Messages for OpenCode Go minimax-m2.7; openai-completions now returns ModelProtocolUnsupported
- Remove test for opencode-go deepseek-v4-flash, which is no longer listed
2026-10-14 10:10:28 +02:00
David Brailovsky
34a8bb38b2 fix(ai): drop images from Anthropic error tool results
Anthropic rejects error tool results whose content is not all text, which
stalled sessions after a failed codemode script that had shown images.
Images in error tool results are now replaced with a text note.

fixes #10794
2026-10-13 15:45:12 +02:00
David Brailovsky
4b994d5e5e fix(ai): repair invalid \u escapes in tool-call arguments
Invalid \u escapes such as \usepackage were treated as valid, so JSON
parsing failed and the partial-json fallback dropped the field and
everything after it. They are now kept as literal text like other
invalid escapes.

fixes #10868
2026-10-13 15:36:01 +02:00
David Brailovsky
7bd01fb924 fix(ai): retry "The upstream provider returned an error"
closes #10842
2026-10-12 18:24:53 +02:00
Armin Ronacher
d4d53612be fix(ai): fall back on Anthropic strict tool limits
Retry known strict tool capacity rejections once with preferred tools using their original non-strict schemas. Preserve required constraints and payload hooks, and never retry a started stream.

closes #10409
2026-10-12 17:20:36 +02:00
David Brailovsky
ee409a85f6
feat(ai,coding-agent): list only the OpenRouter models a key may use (#10672)
On each OpenRouter refresh, combine the catalog (built-in models plus a newer pi.dev catalog) with GET /models/user for the user's key: keep only the listed chat models and take their context size, max output and prices from it. Image and classifier models are kept. The combined list is saved and replaces the OpenRouter catalog, including offline.

- ai: add personalizeOpenRouterCatalog and RefreshModelsContext.baseUrl, so the request follows a base URL configured in models.json (regional endpoints)
- ai: share OpenRouter price conversion with the generator; negative prices (router models such as openrouter/auto) become 0
- coding-agent: add withPersonalizedCatalog next to withRemoteCatalog
- docs: OpenRouter section in providers.md

fixes #10353
2026-10-12 10:17:40 +02:00
Mario Zechner
0f4b1465a4 fix(ai): send the codex request body as a Uint8Array<ArrayBuffer> copy
fetch accepts it as a body under the DOM lib too. From pi-private e97c4a60e.
2026-10-11 16:35:13 +00:00
David Brailovsky
6b07b4e572 fix(ai): apply Mistral request timeout only to response headers
Long Mistral streams (e.g. extended thinking) were aborted once timeoutMs
elapsed, even while data was still arriving. The timeout now covers only
waiting for response headers; body stalls are left to the HTTP client idle
timeout.

fixes #10609
2026-10-08 10:24:04 +02:00
Christian Klotz
7f9e1198f2
feat(coding-agent): publish configuration schemas (#9880)
* feat(coding-agent): publish configuration schemas

* fix(coding-agent): preserve config schema metadata

* fix(coding-agent): align schemas with public contracts

* fix(ai): place compatibility schema with providers

* fix(ai,coding-agent): align configuration schema contracts

* fix(ai): reuse session affinity schema

* docs(coding-agent): document strict theme validation

* fix(ai,coding-agent): align runtime and generated schemas

* fix(ai,coding-agent): centralize configuration schema contracts

* fix(ai,coding-agent): preserve schema validation contracts

* fix(coding-agent): validate keybinding schema grammar

* fix(ai,coding-agent): refine configuration schemas

* fix(ai,tui,coding-agent): align schemas with runtime behavior

* fix(tui): normalize shifted symbol keybindings

* fix(ai): preserve compatibility auto-detection
2026-10-08 10:01:17 +02:00
Armin Ronacher
f76c1db662 feat(ai): add Claude Haiku 5.5
Adds claude-haiku-5-5 to the anthropic provider with its prompt-length pricing tier (5x above 100k input tokens), adaptive thinking with xhigh/max effort, per-message effort, and mid-conversation system messages and tool changes. Bedrock requests detect Haiku 5.5 for adaptive thinking, native xhigh, block binding, and prompt caching.
2026-10-07 21:44:58 +02:00
Mario Zechner
7fb59f995b fix(ai): retry Mistral finish_reason error
Mistral reports transient server failures as finish_reason "error". Its
error message now includes "server error" so the retry classifier treats
it as retryable.

closes #10487
2026-10-07 09:06:19 +00:00
Mario Zechner
8d8ae2fc24 fix(ai): fall back to a free port for Anthropic OAuth callback
Try 53692 first so forwarded setups keep working, then let the OS pick a
free loopback port when 53692 cannot be bound (e.g. Hyper-V/WSL port
exclusions on Windows). The authorize URL, paste prompt and token exchange
use the bound redirect URI.

closes #10571
2026-10-07 07:47:22 +00:00
Armin Ronacher
6b58544540 fix(ai): type empty OpenAI classifier catalogs 2026-10-07 08:35:06 +02:00
Armin Ronacher
ce8972a0e9 feat(ai,coding-agent): add OpenAI Decisions classifier and classifier images
Adds the openai-decisions classifier API with gpt-6-luna on the openai provider. bool questions map to predicates with the criteria in the instructions, refusals fail the result, and gateway timeouts (504) are not retried. The model needs an API key and is hidden from availability while openai uses Sign in with ChatGPT.

Adds optional images to ClassifierContext. Models without image input and APIs that cannot send images return an error result. Codemode's models.classify() accepts images.

Moves the shared classifier HTTP code out of system-one-shared.ts into classifier-shared.ts.
2026-10-07 08:27:56 +02:00
Mario Zechner
fe11328b0d fix(ai): compare faux prompt-cache usage per message
The faux provider's prompt-cache usage estimate compared the previous and
current prompt as two joined strings, character by character, on every
call. It now compares the prompts message by message and compares
characters only from the first differing message. Usage numbers are
unchanged.
2026-10-06 20:58:35 +02:00
Mario Zechner
f284a2460c fix(ai): time responses with private fields; use the real stream in proxy and test mocks
Replaces the WeakMap workaround. streamProxy and the agent and coding-agent test
mocks reimplemented AssistantMessageEventStream as plain EventStream subclasses;
they now use createAssistantMessageEventStream().
2026-10-06 20:26:27 +02:00
Mario Zechner
3ba22ce17a fix(ai): keep AssistantMessageEventStream assignable from plain event streams
The response timing added private fields, which made test and host streams built on
EventStream no longer assignable to StreamFn. Keep the start in a WeakMap instead.
2026-10-06 20:08:35 +02:00
Armin Ronacher
43d3763991 fix(ai): replace bundled Radius catalog with gateway catalog
Models disabled by an organization owner were still listed because the
fetched catalog was merged into the shipped default catalog.
2026-10-06 19:46:54 +02:00
Mario Zechner
36a686ee8d feat: record response, tool execution, and durable task times
pi-ai's AssistantMessageEventStream sets AssistantMessage.durationMs with a
monotonic clock for every API implementation. The agent loop and the durable
tool task set ToolResultMessage.durationMs around execute(). Durable task
records carry startedAt/endedAt stamped by the Session. The coding agent's bash
renderer shows Took from the recorded duration, also after a reload.

closes #10549
2026-10-06 19:28:54 +02:00