* fix(mistral): use truncateUtf16Safe for error text truncation
Replace naive .slice(0, maxChars) with truncateUtf16Safe() in
truncateErrorText() to prevent surrogate pair splitting in
Mistral provider error messages shown to users.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ai): keep Mistral errors UTF-16 safe
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(secrets): resolve SecretRef model credentials at egress via process-local sentinels
SecretRef-managed model-provider credentials now travel as opaque
oc-sent-v1 sentinels through auth storage, stream options, and SDK
config; the guarded model fetch injects real values into headers and
URLs immediately before the SSRF-guarded send and fails closed on
unknown sentinels. packages/ai adapters converge on the host guarded
fetch where the SDK supports custom fetch and unwrap at construction
where it does not. Resolved values (and their percent-encoded forms)
register for exact-value log redaction. Kill switch:
OPENCLAW_SECRET_SENTINELS=off. Also fixes a pre-existing unhandled
rejection race in capNonOkResponseBodyLazily (pipeThrough writer leak).
* test(plugin-sdk): update public surface budget
* fix(anthropic): resolve thinking as disabled when legacy budget is below 1024
When adjustMaxTokensForThinking collapses the thinking budget below the
Anthropic minimum (1024), option resolution now sets thinkingEnabled to
false instead of always forcing it to true. This keeps every downstream
consumer (payload, replay, temperature, tool-choice) consistent — they
all see the same disabled state instead of an enabled flag with a
missing or API-rejected thinking block.
|| → ?? in both builders is defensive: the resolution layer already
prevents invalid budgets from reaching the builder through the normal
path, but ?? preserves an explicit zero when the builder is called
directly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(anthropic): guard raw streamAnthropic builder path against sub-minimum budgets
Add budget guards in both Anthropic payload builders so direct
streamAnthropic and bundled-plugin callers (e.g. Mantle) that bypass
option resolution also get the disabled-state rule instead of producing
API-rejected { type: "enabled", budget_tokens: < 1024 } requests.
Add proof-anthropic-thinking-budget.mts driving real production
functions with terminal output and negative control.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(anthropic): normalize legacy thinking budgets
Co-authored-by: Pick-cat <huang.ting3@xydigit.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The active user turn was decorated at request-build time with an inbound
metadata block (Conversation info / reply-target / sender / forwarded /
chat-history), but the session store persists the bare text and the LLM boundary
strips that block from historical replay. So a user message's serialized bytes
changed retroactively — decorated as the active turn, bare as history — on every
turn, invalidating any prefix/exact-match provider prompt cache from that point
onward, every turn. (#90811 fixed the timestamp half of this same asymmetry.)
Rework of the earlier "strip the active turn" approach (which lost model-visible
context — ClawSweeper's P1) into one that keeps the context AND recovers the
cache cost:
- Route current-turn inbound metadata out of the user prompt and into the hidden
runtime-context custom message (the mechanism from openclaw#89428), and
relocate that carrier to the ABSOLUTE TAIL of the wire request (after the
active user turn and any tool-call scaffolding). The user turn stays bare and
byte-identical in both the active and historical positions; the volatile
carrier vanishes next turn exactly where the assistant reply begins anyway, so
the request is an append-only prefix-extension through the active user turn.
- A durable marker (UserMessage.runtimeContextCarrier, set by convertToLlm from
the carrier's details) lets the Anthropic SDK transport, the managed Anthropic
transport (anthropic-transport-stream / anthropic-payload-policy), and
OpenAI-completions skip the carrier when selecting the deepest cache_control
breakpoint, keeping the anchor on the last stable user turn.
Storage stays BARE (invariant preserved); runtime-only turns (room events) keep
their existing inline behavior, which is byte-stable because room-event/system
context is not strip-eligible; legacy bare-stored sessions are unchanged.
Reviewed with adversarial gpt-5.5/codex sweeps (correctness, byte-identity,
compatibility, test adequacy): found + fixed the managed Anthropic transport
cache-anchor path and added the missing OpenAI-completions/managed-transport
breakpoint tests; a theoretical runtime-only mutation was verified unreachable
and pinned with a test. Final sweep clean.
Co-authored-by: openclaw#89428 (runtime-context handoff approach)
The ChatGPT backend routes requests by session_id (codex-cli sends it); without it each request lands on an arbitrary machine and the prompt cache misses. Measured on live traffic: 56.0% -> 81.8% TRUE cache hit rate (12-turn A/B, identical prompts).
* fix: preserve Bedrock live API providers
* fix: derive Bedrock smoke region from AWS config
* fix: honor Bedrock discovery region in smoke
* fix: keep Bedrock live smoke on Bedrock runtime
---------
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
* fix(mistral): correct model id typo in usesReasoningEffort helper
* chore: re-trigger CI
* test(mistral): cover medium reasoning effort
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* refactor: extract reusable AI runtime package
* refactor: complete AI provider relocation
* refactor: keep llm core internal
* refactor(ai): make @openclaw/ai self-contained with host policy ports
Move pure transport helpers (tool projections, strict-schema normalization,
prompt-cache boundary, stream guards, anthropic/openai compat, request
activity) from src into packages/ai; move utf16-slice into
normalization-core. Inject host policy (guarded fetch, redaction,
strict-tool defaults, diagnostics logging) through AiTransportHost with
inert library defaults installed by src/llm/stream.ts. Narrow the public
barrel to instance-scoped createApiRegistry/createLlmRuntime; the
process-default runtime moves behind internal/ and
registerBuiltInApiProviders takes an explicit registry. Delete the
src/llm/api-registry re-export facade.
* fix(ai): teach node, jiti, and vite resolvers the @openclaw/ai and utf16-slice subpaths
The workspace alias tables in root-alias.cjs, plugin-sdk-native-resolver,
sdk-alias, the shared vitest config, and the Control UI vite config only
knew @openclaw/llm-core; Node-side plugin loading resolved @openclaw/ai
through the pnpm symlink to the unbuilt dist (checks-node-compact CI
failures), and the Control UI build broke on the new
normalization-core/utf16-slice subpath.
* chore(ui): drop leftover service-worker debug logging
* build(release): ship @openclaw/ai with its own shrinkwrap and honest dependency set
packages/ai declares only its six real runtime deps (kysely, chalk, json5,
tslog, zod, fs-safe, and proxyline were never imported); orphaned root deps
removed. generate-npm-shrinkwrap now treats publishable packages/* like
publishable plugins so the AI tarball pins its transitive tree even though
workspace deps are omitted from the root shrinkwrap. knip learns the
package entry points; the tsdown dts neverBundle option moves to its
documented deps.dts home; the README documents the no-semver internal/*
contract and host ports.
* docs(ai): add minimal external-consumer example app
examples/ai-chat consumes only the public @openclaw/ai surface (built dist
via the workspace link): isolated runtime, built-in provider registration,
one streamed completion. Supports Anthropic/OpenAI via env keys and a
keyless local Ollama target; live-verified against Ollama.
* docs(ai): document the @openclaw/ai package and workspace shrinkwrap boundary
* chore(check): include examples/ in duplicate-scan targets
* fix: emit normalization package subpaths
* fix: complete AI package boundary artifacts
* fix: align AI package boundary contracts
* fix(ci): stabilize package release contracts
* test: align documentation contract checks
* test: keep cron docs guard aligned
* test: align restored docs contract guards
* test: follow upstream docs contracts
* docs: drop superseded talk wording