Gemini can attach thoughtSignature to a part whose visible text is
empty and requires it echoed back; convertMessages dropped such blocks
together with the signature, breaking the reasoning chain. With the
chain broken, gemini flash models intermittently end a mid-task turn
with a thought-only STOP and no tool call. Empty blocks are now dropped
only when they are unsigned, mirroring the anthropic converter.
Co-authored-by: Jingtao Xu <jingtaozf@gmail.com>
pickBodyText serialized any non-empty object into the error body. SDK error
fields can hold class instances instead of parsed bodies - AWS SDK v3's
$response.body is an HTTP stream/response wrapper - and stringifying one
produced garbage like {"_events":...} as the body, which then replaced
error.message in the composed display string. error.message is where the SDK
puts the real deserialized exception text ("Input is too long...", schema
validation details), so the one useful string was discarded for noise.
The existing pipe-based stream sniffing only covers node-style streams; web
ReadableStreams (pipeTo/pipeThrough, no pipe) and non-stream wrapper classes
slipped through. Require Object.prototype/null prototype before serializing
a candidate body - parsed JSON bodies are plain objects by construction, so
real gateway bodies still surface.
* feat(ai): preserve structured metadata for Bedrock provider errors
A failed Bedrock turn only exposed `errorMessage`, so downstream runtimes
could not classify a provider failure or correlate it with AWS support
without parsing that string. The AWS SDK carries the HTTP status, the
modeled error code, and the request id on the thrown exception; the
provider dropped all three.
Attach them as a `bedrock_response_failure` diagnostic on the existing
`AssistantMessage.diagnostics` array. No new core type surface, and no
existing line changed: the diff is purely additive.
`errorMessage` stays byte-identical because `isRetryableAssistantError`
classifies retries by string-matching it, and `BEDROCK_ERROR_PREFIXES` is
documented as load-bearing for that.
Scope note: the serialized-stream noise in the original report
(`Validation error: 400: {"_events":...}`) was already fixed in v0.82.1 by
#7081, which added the unread-stream guard in `normalizeProviderError`.
Verified against main: `errorMessage` is clean today. This change covers
only the remaining half of the issue, the missing structured metadata.
Details:
- Carries `details` only, no `error` block, as `pi_messages_rewrite` does.
A modeled mid-stream exception is not an `Error` at all, so routing it
through `extractDiagnosticError` would record `"[object Object]"` plus a
stack trace pointing into the SDK. The readable text is in `errorMessage`.
- `errorCode` reads `error.name`, which is where the SDK puts the modeled
code for both paths that expose one: service exceptions from
`client.send()` (via `x-amzn-errortype`) and the plain `Error` the
event-stream unmarshaller throws for an unmodeled stream error (via
`:error-code`). All 13 modeled Bedrock errors end in `Exception`, so that
suffix is the gate; it excludes transport failures such as `TimeoutError`
and `CredentialsProviderError`, and the SDK's `Unknown` placeholder,
without enumerating them.
- A modeled mid-stream exception reports only a request id, on purpose.
`@smithy/core` `getMessageUnmarshaller` throws `deserializedException[code]`
after reading the code into a local, and the JSON shape deserializer built
that value as a bare object literal, so the code no longer exists by the
time it reaches us. The request id from the initial response is used as a
fallback so these failures stay correlatable.
- `details.status` matches the key `pi_messages_response_failure` uses.
- Header-derived values are dropped when over-long rather than truncated: a
truncated request id is not a request id.
- Aborted turns emit no diagnostic.
closes#7224
* docs(ai): trim comment density in the Bedrock failure diagnostic
The added block sat at 52 percent comment against 14 percent for the rest
of bedrock-converse-stream.ts and 2-9 percent across the other providers.
Cut to the facts a future maintainer cannot infer from the code: why the
error code is not gated on `BedrockRuntimeServiceException`, why a modeled
mid-stream exception yields only a request id, and the `errorMessage`
byte-identity invariant. Now 29 percent of a 27-line block.
Test comments trimmed the same way, 15 percent to 4 percent, matching
bedrock-custom-headers.test.ts. No behavior change; all tests unchanged
and passing.
---------
Co-authored-by: Brian Stanley <brian.stanley@kavak.com>
Prefer function payloads when malformed OpenAI-compatible tool-call deltas also include an empty custom object, preserving parsed arguments.
Fixes#7160
Co-authored-by: Sunny Young <isunnyyoung@gmail.com>
Adds AssistantMessage.rawStopReason and preserves provider original stop reasons.
Improves certain cases of error messages.
For mistral, change unmapped finish reasons to be "error" instead of "stop".
fixes#7255
Bump TypeBox to 1.3.7 so compiled validators guard array-specific keywords when null is accepted, and cover tool-argument validation with a regression test.
Breaking for extensions: TypeBox 1.3 removes deprecated APIs including Type.Base, Type.Awaited, Type.Promise, Type.AsyncIterator, Type.Iterator, Type.Options, and Value.Mutate. Extensions using these through Pi's bundled TypeBox aliases must migrate to supported TypeBox APIs.
Fixes#7003
* fix(ai): send max_tokens for Z.AI providers
Z.AI (zai, zai-coding-cn, api.z.ai, open.bigmodel.cn) ignores
max_completion_tokens and only honors max_tokens, so the configured
output cap never applied and Z.AI fell back to its 65536 default.
Add isZai to detectCompat's useMaxTokens condition.
* fix(ai): set zai max_tokens compat in model generator
Per review feedback: the model generator now maps Z.AI to max_tokens, so
regenerated Z.AI model entries carry compat.maxTokensField="max_tokens"
(Z.AI ignores max_completion_tokens). The runtime detectCompat change is
kept as a fallback for custom/self-hosted Z.AI base URLs not in the catalog,
matching how moonshot/together/nvidia are handled in both places.
Bedrock profile configured via pi's auth flow was ignored when ambient
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY were set, because pi forced
config.credentials from those keys and the SDK treats explicit
credentials as overriding config.profile.
Fixes#6957
The OpenRouter PKCE flow only completed through the loopback callback
server, so remote/headless sessions (e.g. pi over SSH) could never
finish login: the browser runs on another machine and cannot reach
127.0.0.1 on the host running pi.
Race the callback server against a manual_code prompt, matching the
existing Anthropic and OpenAI Codex flows. Users can paste the final
redirect URL (or the bare authorization code) and the flow exchanges
it with the same PKCE verifier. A claimed callback keeps priority so
a pasted code cannot interrupt an in-flight exchange.
The callback server object follows the OAuthServerInfo shape from the
OpenAI Codex flow: waitForCredential() resolves null when cancelWait()
hands the login over to manual entry, and close() is pure cleanup that
never settles the wait.
Refs #7078
Route claude-opus-5 through the Anthropic Messages API with adaptive
thinking, the Copilot minimal thinking-level override, and pin the
extended 1M context window alongside the other Copilot Opus models.
Add missing entries for the llama.cpp catalog cache and resource loader
directory fix, duplicate ai-side changes into coding-agent, add missing
external PR attributions, restore the 0.82.0 section that was modified
after release, and add the New Features section.
Add the constrained sampling API and provider plumbing for OpenAI and Anthropic tool calls, including grammar sampling variants and model capability metadata.