Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
9daf46b
fix(session-ui): align provider error icon with first line (#52859)
opencode-agent[bot] Oct 6, 2026
cb11287
fix(app): use HTTP-safe UUID for attachment uploads (#51783)
opencode-agent[bot] Oct 6, 2026
b78d10c
fix(server): always report opencode as mcp client name (#53471)
opencode-agent[bot] Oct 6, 2026
8f0f4be
fix(script): normalize local build channels (#53461)
vegerot Oct 6, 2026
2bddf58
refactor(client): share the registered service decision between clien…
kitlangton Oct 6, 2026
cabb5d0
fix(core): migrate the v1 thinking block-binding opt-out (#52327)
dmitrii-galantsev Oct 6, 2026
81b92c0
feat(core): enable portable shell scanner on local and dev channels (…
kitlangton Oct 6, 2026
89a8f1a
docs(go): document paid Space Bunny (#53487)
MrMushrooooom Oct 6, 2026
50ae4bd
chore: remove unused dependencies from V2 packages (#53482)
kitlangton Oct 6, 2026
6e4de80
chore: update nix node_modules hashes
opencode-agent[bot] Oct 6, 2026
da20865
fix(app): use HTTP-safe UUID for restored comment notes (#53501)
opencode-agent[bot] Oct 6, 2026
2eff7da
fix(app): allow touch scrolling in directory picker (#51781)
opencode-agent[bot] Oct 6, 2026
8eb2ebd
fix(app): disable submit hover on touch (#49461)
opencode-agent[bot] Oct 6, 2026
b162929
feat(desktop): redesign the browser bar (#53488)
Hona Oct 6, 2026
592daf2
fix(app): refine project selector menu (#52349)
iamdavidhill Oct 6, 2026
80c4a6b
feat(app): polish mobile session navigation and drawers (#53267)
Brendonovich Oct 6, 2026
a7523ac
fix(ui): render dollar math with marked-katex-extension (#53513)
opencode-agent[bot] Oct 6, 2026
184890e
chore: update nix node_modules hashes
opencode-agent[bot] Oct 6, 2026
fb72e25
fix(acp): wait for a cold location's plugins before the first catalog…
nexxeln Oct 6, 2026
f6ae986
fix(acp): report tool names and full-file diffs (#53360)
nexxeln Oct 6, 2026
f17b15e
fix(acp): tighten cwd, cancel, delete, and terminal auth handling (#5…
nexxeln Oct 6, 2026
ba281e7
docs(console): add workspace, providers, and api docs (#53531)
vimtor Oct 6, 2026
6bffe79
chore(core): refresh bundled models.dev snapshot
opencode-agent[bot] Oct 6, 2026
de2cacd
fix(docs): redirect inference help link
adamdotdevin Oct 6, 2026
ed0e7ac
fix(acp): reject a mismatched fork cwd and resolve overlapping provid…
nexxeln Oct 6, 2026
c2f8785
fix(core): drop tool name and namespace length caps (#53586)
opencode-agent[bot] Oct 6, 2026
ccb5162
fix(tui): match execute dialog to two-toned dialog layout (#53584)
thdxr Oct 6, 2026
36a3805
feat(core): stop asking for external directory access by default (#53…
thdxr Oct 6, 2026
4a0f273
feat(cli): include every pairing address in QR codes (#53588)
thdxr Oct 6, 2026
59e5953
fix(schema): expose effort-update and thinking-binding overrides on m…
argszero Oct 6, 2026
9ee97d8
fix(sdk): release completed embedded request signals (#53613)
kitlangton Oct 6, 2026
d174977
docs(www): add Exo Free to Console models (#53620)
dc85 Oct 6, 2026
e41256e
fix(desktop): fix browser bar review findings (#53627)
Hona Oct 6, 2026
8b02c4e
fix(ai): drop empty unfinished reasoning items on replay (#53603)
rekram1-node Oct 7, 2026
9c51d84
feat(app): preview Word, Excel and PowerPoint files (#53305)
Hona Oct 7, 2026
d5cf9c2
chore: update nix node_modules hashes
opencode-agent[bot] Oct 7, 2026
369aad3
feat(core): add external credential references (#53624)
rekram1-node Oct 7, 2026
74678c4
feat(server): stream fs.read responses with HTTP Range support (#53088)
kitlangton Oct 7, 2026
56c2f43
docs(www): sync Mistral Large 4 pricing and privacy
fwang Oct 7, 2026
8b45c56
fix(cli): embed the web UI as raw bytes instead of base64 strings (#5…
Hona Oct 7, 2026
a7f3d57
fix(cli): compress the embedded web UI at brotli's best quality (#53644)
Hona Oct 7, 2026
a90ad85
fix(cli): serve the embedded web UI brotli-compressed (#53645)
Hona Oct 7, 2026
a2b6005
fix(desktop): delete old staged CLI versions (#53659)
Hona Oct 7, 2026
eebe9d3
fix(app): handle one-time pairing links across the GUI (#53257)
Hona Oct 7, 2026
1d610f9
fix(ui): support inline custom answers for string choice fields in co…
rekram1-node Oct 7, 2026
fd99516
fix(session-ui): keep Markdown text selectable (#53665)
opencode-agent[bot] Oct 7, 2026
34cf183
feat(ai): support between_tools thinking in Anthropic Messages (#53601)
rekram1-node Oct 7, 2026
6273bd7
fix(core): stop sending API key metadata as request body (#53546)
opencode-agent[bot] Oct 7, 2026
f655e7b
fix(app): wrap mobile project controls responsively (#50484)
opencode-agent[bot] Oct 7, 2026
2e3d3a7
fix(app): use filled clear icon in model and branch searches (#53511)
opencode-agent[bot] Oct 7, 2026
10b11ba
fix(app): keep live terminals through transient connect failures (#52…
opencode-agent[bot] Oct 7, 2026
6921947
feat(app): show recently closed tabs menu (#51973)
iamdavidhill Oct 7, 2026
68aa4f8
feat(app): show recent sessions in the empty command palette (#53727)
opencode-agent[bot] Oct 7, 2026
e1b7299
chore(core): refresh bundled models.dev snapshot
opencode-agent[bot] Oct 7, 2026
119cb15
refactor(app): remove configurable default server (#53731)
opencode-agent[bot] Oct 7, 2026
e297903
fix(core): bump session time_updated on step lifecycle events (#49105)
Namit2003 Oct 7, 2026
70c6c8c
feat(integration): add declarative external connection method (#53672)
rekram1-node Oct 7, 2026
d5154fb
fix(core): bypass gray-matter content cache when parsing frontmatter …
holny Oct 7, 2026
8bd8960
fix(ai): add missing OpenAI and Google service tiers (#53753)
opencode-agent[bot] Oct 7, 2026
053e534
refactor(server): align pty connect preflight order (#53579)
thdxr Oct 7, 2026
5183ea4
fix(core): send Codex session headers with ChatGPT token sharing (#53…
opencode-agent[bot] Oct 7, 2026
554e7c2
docs(www): add Haiku 5.5 to V2 model tables
fwang Oct 7, 2026
56be008
feat(core): add AWS profile setup for Bedrock (#53626)
rekram1-node Oct 7, 2026
cab7e2c
docs(www): add Haiku 5.5 to V2 Go guide
fwang Oct 7, 2026
31bdd8e
fix(core): only send png, jpeg and webp images to xAI (#53787)
Jaaneek Oct 7, 2026
a98adc1
fix(tui): stop dialogs reading stale transcript rows after resync (#5…
simonklee Oct 7, 2026
8587f81
fix(tui): separate queued prompt dock from composer (#53801)
opencode-agent[bot] Oct 7, 2026
b8d3c92
fix(cli): keep data and config on uninstall
thdxr Oct 7, 2026
613ef8b
feat(core): enforce managed integration and tool policies (#53775)
adamdotdevin Oct 7, 2026
d016db8
feat(core): enforce skill integration policies
adamdotdevin Oct 7, 2026
e3419d4
fix(cli): keep state on uninstall
thdxr Oct 7, 2026
fcad131
chore: upgrade opentui v0.5.16 (#53805)
simonklee Oct 7, 2026
98d57d0
fix(app): focus search input when opening /model dialog (#53707)
Hona Oct 7, 2026
2138bfd
chore: update nix node_modules hashes
opencode-agent[bot] Oct 7, 2026
87a1ebc
feat(core): connect Azure through the Azure CLI as an external creden…
neriousy Oct 7, 2026
162bc69
test: stabilize asynchronous UI and plugin tests (#53818)
simonklee Oct 7, 2026
be19c76
fix(core): drop early guarded plugin activation (#53819)
rekram1-node Oct 7, 2026
46c69bc
fix(session-ui): show latest thinking summary heading while streaming…
Hona Oct 7, 2026
c28e9ec
fix(cli): give plugins the host's Effect (#53422)
kitlangton Oct 7, 2026
6297cd5
feat(ui): animate segmented control with solid-motion and fix button …
kitlangton Oct 8, 2026
216c311
chore: update nix node_modules hashes
opencode-agent[bot] Oct 8, 2026
0d07f91
feat(cli): pair remotely through OpenTunnel (#53837)
thdxr Oct 8, 2026
5d05835
chore: update nix node_modules hashes
opencode-agent[bot] Oct 8, 2026
3b684ac
fix(app): anchor revealed tools under the sticky headers (#53832)
Hona Oct 8, 2026
53533fd
fix(cli): share the device's OpenTunnel tunnel for remote access (#53…
thdxr Oct 8, 2026
6861df7
fix(cli): reject stale platform binaries during install (#53845)
thdxr Oct 8, 2026
8f48618
docs(www): add Sonnet 5.5 to V2 model tables
fwang Oct 8, 2026
c9e1ed7
fix(core): reject malformed tool arguments and settle unfinished call…
rekram1-node Oct 8, 2026
a80ca68
fix(app): focus session search when the Home shortcut opens Home (#53…
opencode-agent[bot] Oct 8, 2026
f108fa4
fix(core): keep last known remote config when refresh fails (#53667)
Ayushlm10 Oct 8, 2026
a19285d
fix(core): bundle compatible provider entrypoints (#53854)
rekram1-node Oct 8, 2026
9e100f4
fix(ui): render dollar math next to punctuation without matching pric…
opencode-agent[bot] Oct 8, 2026
d869df3
chore: update nix node_modules hashes
opencode-agent[bot] Oct 8, 2026
93afa91
fix(core): default Vertex location to global (#53872)
rekram1-node Oct 8, 2026
434f7b2
fix(app): derive waiting steer presentation from execution state (#53…
opencode-agent[bot] Oct 8, 2026
2f1a0e7
fix(tui): open clicked transcript links, including wrapped rows (#53903)
simonklee Oct 8, 2026
da40b8f
chore: upgrade opentui v0.5.17 (#53904)
simonklee Oct 8, 2026
62e087f
sync release versions for v2.0.25 [skip ci]
Oct 8, 2026
0621ea9
chore: update nix node_modules hashes
opencode-agent[bot] Oct 8, 2026
cfb02b2
chore(sync): merge upstream v2 through v2.0.25
cursoragent Oct 8, 2026
2c88675
fix(sync): narrow external credentials in fork auth paths
cursoragent Oct 8, 2026
741e826
test(sync): expect external connect and Shuvcode ACP login
cursoragent Oct 8, 2026
56f082d
fix(cli): point remote access at ShuvTunnel
cursoragent Oct 8, 2026
921ba6d
fix(cli): use shuvcode in pairing and remote-access hints
cursoragent Oct 8, 2026
b76fda0
fix(cli): name ACP terminal auth for Shuvcode
cursoragent Oct 8, 2026
1e15f94
fix(server): report shuvcode as the MCP client
cursoragent Oct 8, 2026
119a26e
fix(script): sanitize preview channel names
cursoragent Oct 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
148 changes: 81 additions & 67 deletions bun.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bunfig.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
exact = true
# Only install newly resolved package versions published at least 3 days ago.
minimumReleaseAge = 259200
minimumReleaseAgeExcludes = ["@agentclientprotocol/sdk", "@ai-sdk/amazon-bedrock", "@ai-sdk/anthropic", "@brendonovich/vite-plugin-opencode", "@opencode/sdk", "@opencode-ai/pty", "@opencode-ai/pty-darwin-arm64", "@opencode-ai/pty-darwin-x64", "@opencode-ai/pty-linux-arm64-gnu", "@opencode-ai/pty-linux-arm64-musl", "@opencode-ai/pty-linux-x64-gnu", "@opencode-ai/pty-linux-x64-musl", "@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-arm64-musl", "@opentui/core-linux-x64", "@opentui/core-linux-x64-musl", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid", "opentui-spinner", "gitlab-ai-provider", "opencode-gitlab-auth", "@ff-labs/fff-node", "@ff-labs/fff-bun", "@ff-labs/fff-bin-darwin-arm64", "@ff-labs/fff-bin-darwin-x64", "@ff-labs/fff-bin-linux-arm64-gnu", "@ff-labs/fff-bin-linux-arm64-musl", "@ff-labs/fff-bin-linux-x64-gnu", "@ff-labs/fff-bin-linux-x64-musl", "@ff-labs/fff-bin-win32-arm64", "@ff-labs/fff-bin-win32-x64", "@pierre/diffs", "@pierre/theming", "app-builder-lib", "dmg-builder", "electron", "electron-builder", "electron-publish", "blume", "mermaid"]
minimumReleaseAgeExcludes = ["@agentclientprotocol/sdk", "@ai-sdk/amazon-bedrock", "@ai-sdk/anthropic", "@brendonovich/vite-plugin-opencode", "@kitlangton/solid-motion", "@opencode/sdk", "@opencode-ai/pty", "@opencode-ai/pty-darwin-arm64", "@opencode-ai/pty-darwin-x64", "@opencode-ai/pty-linux-arm64-gnu", "@opencode-ai/pty-linux-arm64-musl", "@opencode-ai/pty-linux-x64-gnu", "@opencode-ai/pty-linux-x64-musl", "@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-arm64-musl", "@opentui/core-linux-x64", "@opentui/core-linux-x64-musl", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid", "@opentunnel/client", "@opentunnel/protocol", "opentui-spinner", "gitlab-ai-provider", "opencode-gitlab-auth", "@ff-labs/fff-node", "@ff-labs/fff-bun", "@ff-labs/fff-bin-darwin-arm64", "@ff-labs/fff-bin-darwin-x64", "@ff-labs/fff-bin-linux-arm64-gnu", "@ff-labs/fff-bin-linux-arm64-musl", "@ff-labs/fff-bin-linux-x64-gnu", "@ff-labs/fff-bin-linux-x64-musl", "@ff-labs/fff-bin-win32-arm64", "@ff-labs/fff-bin-win32-x64", "@pierre/diffs", "@pierre/theming", "app-builder-lib", "dmg-builder", "electron", "electron-builder", "electron-publish", "blume", "mermaid"]

[test]
root = "./do-not-run-tests-from-root"
6 changes: 3 additions & 3 deletions nix/hashes.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"nodeModules": {
"x86_64-linux": "sha256-WUbRqGy4Ii3292cxriHnGH5GzdsEWgMmK6UbvcyFM0c=",
"aarch64-linux": "sha256-xCLH8I70g6dDYuoTSRO0Bu4jUEhm/UJh8F0plpTmwME=",
"aarch64-darwin": "sha256-Y/n8WPeHHx7DfXnNR+bSJKBdjZbYFzHr9P2mX0ODg28="
"x86_64-linux": "sha256-6+0Lqv+/nZL4+9QF2SVh21VqMhicCnt84lUvlxoQxjc=",
"aarch64-linux": "sha256-tmKG8kZUa6w6PlX1dKeeNY4dYH6wPV4gBs6ynbvLwz4=",
"aarch64-darwin": "sha256-Khb55UlgZo39zOQ7OCkJXJ8Zottv7SQy3D8N8nlOn/I="
}
}
12 changes: 7 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/package.json",
"name": "opencode",
"description": "AI-powered development tool",
"version": "2.0.24",
"version": "2.0.25",
"private": true,
"type": "module",
"packageManager": "bun@1.4.2",
Expand Down Expand Up @@ -53,9 +53,9 @@
"@octokit/rest": "22.0.0",
"@hono/standard-validator": "0.2.0",
"@hono/zod-validator": "0.4.2",
"@opentui/core": "0.5.14",
"@opentui/keymap": "0.5.14",
"@opentui/solid": "0.5.14",
"@opentui/core": "0.5.17",
"@opentui/keymap": "0.5.17",
"@opentui/solid": "0.5.17",
"@tanstack/solid-virtual": "3.13.37",
"@shikijs/stream": "4.4.3",
"@standard-schema/spec": "1.1.0",
Expand Down Expand Up @@ -183,6 +183,8 @@
"@ff-labs/fff-bun@0.10.5": "patches/@ff-labs%2Ffff-bun@0.10.5.patch",
"ghostty-web@github:anomalyco/ghostty-web#83c0a07": "patches/ghostty-web@0.3.0.patch",
"vite@8.2.2": "patches/vite@8.2.2.patch",
"@modelcontextprotocol/client@2.0.0": "patches/@modelcontextprotocol%2Fclient@2.0.0.patch"
"@modelcontextprotocol/client@2.0.0": "patches/@modelcontextprotocol%2Fclient@2.0.0.patch",
"@opentunnel/protocol@0.2.0": "patches/@opentunnel%2Fprotocol@0.2.0.patch",
"@opentunnel/client@0.2.0": "patches/@opentunnel%2Fclient@0.2.0.patch"
}
}
2 changes: 1 addition & 1 deletion packages/ai/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "2.0.24",
"version": "2.0.25",
"name": "@opencode/ai",
"type": "module",
"license": "MIT",
Expand Down
17 changes: 10 additions & 7 deletions packages/ai/src/protocols/anthropic-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ const AnthropicThinkingEnabled = Schema.Struct({
...AnthropicThinkingFields,
})
const AnthropicThinkingAdaptive = Schema.Struct({ type: Schema.tag("adaptive"), ...AnthropicThinkingFields })
const AnthropicThinkingDisabled = Schema.Struct({ type: Schema.tag("disabled") })
const AnthropicThinkingDisabled = Schema.Struct({ type: Schema.Literals(["disabled", "between_tools"]) })
const AnthropicThinking = Schema.Union([AnthropicThinkingEnabled, AnthropicThinkingAdaptive, AnthropicThinkingDisabled])
type AnthropicThinking = typeof AnthropicThinking.Type

Expand Down Expand Up @@ -1004,18 +1004,21 @@ const lowerMessages = Effect.fnUntraced(function* (request: LLMRequest, breakpoi
// TODO: Move per-model capability heuristics (`supportsEffortUpdates`, `supportsNativeSystemUpdates`,
// `supportsThinkingBlockBinding`) into explicit model/provider `compatibility` metadata so the protocol
// only reads `request.model.compatibility`.
const isThinkingOff = Schema.is(AnthropicThinkingDisabled)

// Per-turn effort started with Claude Opus 5 and every Claude 5.1 model; later versions of any family inherit it.
const supportsEffortUpdates = (model: LLMRequest["model"]) => {
const override = model.compatibility?.supportsEffortUpdates
const supportsEffortUpdates = (request: LLMRequest) => {
if (isThinkingOff(request.providerOptions?.thinking)) return false
const override = request.model.compatibility?.supportsEffortUpdates
if (override !== undefined) return override
const version = claudeVersion(model.id)
const version = claudeVersion(request.model.id)
if (version === undefined) return false
if (version.family === "opus" && version.major >= 5) return true
return version.major > 5 || (version.major === 5 && version.minor >= 1)
}

const applyThinkingBindingDefault = (model: LLMRequest["model"], thinking: AnthropicThinking | undefined) => {
if (thinking?.type === "disabled") return thinking
if (isThinkingOff(thinking)) return thinking
if (!supportsThinkingBlockBinding(model)) return thinking
return {
...(thinking ?? { type: "adaptive" as const }),
Expand Down Expand Up @@ -1610,7 +1613,7 @@ export const protocol = Protocol.make({
}),
step,
},
supportsEffortUpdates: (request) => supportsEffortUpdates(request.model),
supportsEffortUpdates,
})

export const transport = <
Expand Down Expand Up @@ -1656,7 +1659,7 @@ function requiredBetaHeaders(body: Pick<AnthropicMessagesBody, "messages" | "con
betas.push("mid-conversation-output-config-2026-07-01")

const thinking = body.thinking
if (thinking && thinking.type !== "disabled" && thinking.block_binding) betas.push(THINKING_BINDING_BETA)
if (thinking && !isThinkingOff(thinking) && thinking.block_binding) betas.push(THINKING_BINDING_BETA)
return betas
}

Expand Down
4 changes: 3 additions & 1 deletion packages/ai/src/protocols/bedrock-converse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,9 @@ const MIN_THINKING_BUDGET = 1_024

const isThinkingDisabled = Schema.is(
Schema.Struct({
additionalModelRequestFields: Schema.Struct({ thinking: Schema.Struct({ type: Schema.Literal("disabled") }) }),
additionalModelRequestFields: Schema.Struct({
thinking: Schema.Struct({ type: Schema.Literals(["disabled", "between_tools"]) }),
}),
}),
)

Expand Down
2 changes: 1 addition & 1 deletion packages/ai/src/protocols/google-interactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const Options = Schema.Struct({
store: lenient(Schema.Boolean),
thinkingLevel: lenient(ThinkingLevel),
thinkingSummaries: lenient(knownString<"auto" | "none">()),
serviceTier: lenient(knownString<"standard" | "flex" | "priority">()),
serviceTier: lenient(knownString<"standard" | "flex" | "priority" | "deferred">()),
})
export type OptionsInput = typeof Options.Encoded
export type ProviderOptionsInput = OptionsInput
Expand Down
1 change: 1 addition & 0 deletions packages/ai/src/protocols/open-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,7 @@ const lowerReasoning = (part: ReasoningPart, providerMetadataKey: string): OpenR
typeof metadata.reasoningEncryptedContent === "string" || metadata.reasoningEncryptedContent === null
? metadata.reasoningEncryptedContent
: undefined
if (part.text.length === 0 && !encryptedContent) return undefined
return {
type: "reasoning",
...(id === undefined ? {} : { id }),
Expand Down
3 changes: 2 additions & 1 deletion packages/ai/src/protocols/utils/bedrock-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export const defaultChain = (options: DefaultChainOptions): Effect.Effect<Creden
Effect.tryPromise({
try: async () => {
const { fromNodeProviderChain } = await import("@aws-sdk/credential-providers")
const identity = await fromNodeProviderChain(options.profile === undefined ? {} : { profile: options.profile })()
// ignoreCache re-reads shared config and SSO token files, so `aws sso login` takes effect without a restart.
const identity = await fromNodeProviderChain({ ignoreCache: true, profile: options.profile })()
return {
region: options.region,
accessKeyId: identity.accessKeyId,
Expand Down
3 changes: 2 additions & 1 deletion packages/ai/src/protocols/utils/openai-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export type OpenAITextVerbosity = OpenResponsesOptions.TextVerbosity
// in lockstep with `openai-node/src/resources/responses/responses.ts`.
export const OpenAIResponseIncludables = OpenResponsesOptions.ResponseIncludables
export type OpenAIResponseIncludable = OpenResponsesOptions.ResponseIncludable
export const OpenAIServiceTiers = [...OpenResponsesOptions.ServiceTiers, "scale"] as const
// Mirrors OpenAI's `ServiceTier` union from the official SDK.
export const OpenAIServiceTiers = [...OpenResponsesOptions.ServiceTiers, "scale", "fast", "ultrafast"] as const
export type OpenAIServiceTier = (typeof OpenAIServiceTiers)[number] | (string & {})

export const OpenAIReasoningEffort = OpenResponsesOptions.ReasoningEffort
Expand Down
21 changes: 8 additions & 13 deletions packages/ai/src/protocols/utils/tool-stream.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Effect, Option } from "effect"
import { AIError, LLMEvent, type ProviderMetadata, type ToolCall } from "../../schema/index.js"
import { AIError, LLMEvent, type ProviderMetadata, type ToolCall, type ToolInputError } from "../../schema/index.js"
import { eventError, parseToolInput, type ToolAccumulator } from "../shared.js"
import { parse } from "./partial-json.js"

Expand Down Expand Up @@ -79,16 +79,6 @@ const inputDelta = (tool: PendingTool, text: string): LLMEvent => {
const toolCall = (route: string, tool: PendingTool, inputOverride?: string) => {
const raw = inputOverride ?? tool.input
return parseToolInput(route, tool.name, raw).pipe(
Effect.catch((error) =>
tool.providerExecuted
? Effect.fail(error)
: Effect.succeed(
Option.getOrElse(
Option.map(parsePartialInput(raw), (input) => input ?? {}),
() => ({}),
),
),
),
Effect.map(
(input): ToolCall =>
LLMEvent.toolCall({
Expand All @@ -100,10 +90,15 @@ const toolCall = (route: string, tool: PendingTool, inputOverride?: string) => {
providerMetadata: tool.providerMetadata,
}),
),
Effect.catch((error) =>
tool.providerExecuted
? Effect.fail(error)
: Effect.succeed(LLMEvent.toolInputError({ id: tool.id, name: tool.name, namespace: tool.namespace, raw })),
),
)
}

const finishEvents = (tool: PendingTool, event: ToolCall): ReadonlyArray<LLMEvent> => [
const finishEvents = (tool: PendingTool, event: ToolCall | ToolInputError): ReadonlyArray<LLMEvent> => [
LLMEvent.toolInputEnd({
id: tool.id,
name: tool.name,
Expand Down Expand Up @@ -206,7 +201,7 @@ export const appendExisting = <K extends StreamKey>(

/**
* Finalize one pending tool call: parse the accumulated raw JSON, remove it
* from state, and recover incomplete local arguments when needed.
* from state, and reject malformed local arguments without repairing them.
* Missing keys are a no-op because some providers emit stop events for
* non-tool content blocks.
*/
Expand Down
4 changes: 2 additions & 2 deletions packages/ai/src/providers/amazon-bedrock-mantle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ const messagesRoute = Route.make({
...AnthropicMessages.protocol,
// Mantle rejects mid-conversation `output_config` on Opus 5.0; support starts at 5.1+.
supportsEffortUpdates: (request) => {
const override = request.model.compatibility?.supportsEffortUpdates
if (override !== undefined) return override
if (!(AnthropicMessages.protocol.supportsEffortUpdates?.(request) ?? false)) return false
if (request.model.compatibility?.supportsEffortUpdates !== undefined) return true
const version = claudeVersion(request.model.id)
return version !== undefined && (version.major > 5 || (version.major === 5 && version.minor >= 1))
},
Expand Down
2 changes: 1 addition & 1 deletion packages/ai/src/providers/google-vertex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const configuredRoute = (input: Config, modelID: string | ModelID) => {
provider: id,
message: "Google Vertex tuned models do not support Express Mode API keys",
})
const location = GoogleVertexShared.location(inputLocation, "us-central1")
const location = GoogleVertexShared.location(inputLocation, "global")
const project = GoogleVertexShared.project(inputProject)
const endpoint =
baseURL ??
Expand Down
16 changes: 16 additions & 0 deletions packages/ai/test/effort-updates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,22 @@ describe("Anthropic Messages effort updates", () => {
}),
)

it.effect("strips markers when thinking is disabled or between_tools", () =>
Effect.gen(function* () {
const sonnet = anthropic("claude-sonnet-5-5")
const betweenTools = yield* compileRequest(
LLM.request({
model: sonnet,
messages: conversation,
providerOptions: { thinking: { type: "between_tools" }, effort: "low" },
}),
)

expect(systemMessages(betweenTools.body)).toHaveLength(0)
expect(betweenTools.body.output_config).toEqual({ effort: "low" })
}),
)

it.effect("strips markers for Opus 5.0 on Bedrock Mantle Messages while lowering Opus 5.5", () =>
Effect.gen(function* () {
const mantle = AmazonBedrockMantle.configure({ apiKey: "test", region: "us-east-1" })
Expand Down
1 change: 1 addition & 0 deletions packages/ai/test/provider-options/openai.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ LLM.request({ model: selected, prompt: "Hello", providerOptions: { reasoningEffo
LLM.request({ model: selected, prompt: "Hello", providerOptions: { textVerbosity: "low" } })
LLM.request({ model: selected, prompt: "Hello", providerOptions: { textVerbosity: "verbose" } })
LLM.request({ model: selected, prompt: "Hello", providerOptions: { serviceTier: "scale" } })
LLM.request({ model: selected, prompt: "Hello", providerOptions: { serviceTier: "ultrafast" } })
LLM.request({ model: selected, prompt: "Hello", providerOptions: { serviceTier: "future-tier" } })
LLM.request({ model: chat, prompt: "Hello", providerOptions: { reasoningEffort: "max" } })
LLM.request({ model: chat, prompt: "Hello", providerOptions: { reasoningEffort: "experimental" } })
Expand Down
3 changes: 2 additions & 1 deletion packages/ai/test/provider/anthropic-thinking-binding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ it.effect("preserves explicit thinking settings and combines required beta heade
Effect.gen(function* () {
for (const thinking of [
{ type: "disabled" },
{ type: "between_tools" },
{ type: "adaptive", block_binding: { prefix_mismatch_behavior: "error" } },
] as const) {
const request = LLM.request({
Expand All @@ -55,7 +56,7 @@ it.effect("preserves explicit thinking settings and combines required beta heade
const prepared = yield* AnthropicMessages.route.prepareTransport(compiled.body, request)
expect(compiled.body.thinking).toEqual(thinking)
expect(prepared.request.headers["anthropic-beta"]).toBe(
thinking.type === "disabled"
thinking.type === "disabled" || thinking.type === "between_tools"
? "interleaved-thinking-2025-05-14,compact-2026-01-12"
: "interleaved-thinking-2025-05-14,compact-2026-01-12,thinking-binding-controls-2026-08-01",
)
Expand Down
7 changes: 4 additions & 3 deletions packages/ai/test/provider/bedrock-converse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1177,7 +1177,7 @@ describe("Bedrock Converse route", () => {
}),
)

it.effect("recovers incomplete tool input at finalization", () =>
it.effect("rejects incomplete tool input at finalization", () =>
Effect.gen(function* () {
const body = eventStreamBody(
["messageStart", { role: "assistant" }],
Expand All @@ -1194,10 +1194,11 @@ describe("Bedrock Converse route", () => {
)
const response = yield* LLMClient.generate(baseRequest).pipe(Effect.provide(fixedBytes(body)))

expect(response.events.find((event) => event.type === "tool-call")).toMatchObject({
expect(response.toolCalls).toEqual([])
expect(response.events.find((event) => event.type === "tool-input-error")).toMatchObject({
id: "tool_1",
name: "lookup",
input: { query: "partial" },
raw: '{"query":"partial',
})
expect(response.finishReason).toEqual({ normalized: "tool-calls", raw: "end_turn" })
}),
Expand Down
Loading
Loading