@opencode/ai 0.0.0-dev-20271 → 0.0.0-dev-20276
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -13,6 +13,7 @@ import { JsonObject, knownString, optionalArray, optionalNull, ProviderShared }
|
|
|
13
13
|
import { classifyProviderFailure } from "../provider-error.js";
|
|
14
14
|
import { effortUpdate, resolveEffortUpdates } from "../effort-updates.js";
|
|
15
15
|
import * as Cache from "./utils/cache.js";
|
|
16
|
+
import { claudeVersion, supportsThinkingBlockBinding, THINKING_BINDING_BETA } from "./utils/claude-model.js";
|
|
16
17
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
17
18
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
18
19
|
const ADAPTER = "anthropic-messages";
|
|
@@ -850,20 +851,6 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (re
|
|
|
850
851
|
}
|
|
851
852
|
return messages;
|
|
852
853
|
});
|
|
853
|
-
// Accept gateway namespaces and Vertex suffixes without treating a snapshot date as a minor version.
|
|
854
|
-
const claudeVersion = (id) => {
|
|
855
|
-
const match = /(?:^|[./])claude-(?<family>[a-z]+)-(?<major>\d+)(?:[.-](?<minor>\d{1,2}))?(?:$|[-:@])/.exec(id.toLowerCase())?.groups;
|
|
856
|
-
if (!match)
|
|
857
|
-
return undefined;
|
|
858
|
-
return { family: match.family, major: Number(match.major), minor: Number(match.minor ?? 0) };
|
|
859
|
-
};
|
|
860
|
-
const supportsThinkingBlockBinding = (model) => {
|
|
861
|
-
const override = model.compatibility?.supportsThinkingBlockBinding;
|
|
862
|
-
if (override !== undefined)
|
|
863
|
-
return override;
|
|
864
|
-
const version = claudeVersion(model.id);
|
|
865
|
-
return version !== undefined && (version.major > 5 || (version.major === 5 && version.minor >= 1));
|
|
866
|
-
};
|
|
867
854
|
const supportsEffortUpdates = (model) => {
|
|
868
855
|
const override = model.compatibility?.supportsEffortUpdates;
|
|
869
856
|
if (override !== undefined)
|
|
@@ -1437,7 +1424,7 @@ function requiredBetaHeaders(body) {
|
|
|
1437
1424
|
betas.push("mid-conversation-output-config-2026-07-01");
|
|
1438
1425
|
const thinking = body.thinking;
|
|
1439
1426
|
if (thinking && thinking.type !== "disabled" && thinking.block_binding)
|
|
1440
|
-
betas.push(
|
|
1427
|
+
betas.push(THINKING_BINDING_BETA);
|
|
1441
1428
|
return betas;
|
|
1442
1429
|
}
|
|
1443
1430
|
export const route = Route.make({
|
|
@@ -141,7 +141,7 @@ interface ParserState {
|
|
|
141
141
|
readonly hasToolCalls: boolean;
|
|
142
142
|
readonly lifecycle: Lifecycle.State;
|
|
143
143
|
readonly reasoningSignatures: Readonly<Record<number, string>>;
|
|
144
|
-
readonly reasoningRedactedContent: Readonly<Record<number,
|
|
144
|
+
readonly reasoningRedactedContent: Readonly<Record<number, Uint8Array[]>>;
|
|
145
145
|
}
|
|
146
146
|
/**
|
|
147
147
|
* The Bedrock Converse protocol — request body construction, body schema, and
|
|
@@ -9,6 +9,7 @@ import { JsonObject, optionalArray, ProviderShared } from "./shared.js";
|
|
|
9
9
|
import { BedrockAuth } from "./utils/bedrock-auth.js";
|
|
10
10
|
import { BedrockCache } from "./utils/bedrock-cache.js";
|
|
11
11
|
import { BedrockMedia } from "./utils/bedrock-media.js";
|
|
12
|
+
import { supportsThinkingBlockBinding, THINKING_BINDING_BETA } from "./utils/claude-model.js";
|
|
12
13
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
13
14
|
import { MistralToolID } from "./utils/mistral-tool-id.js";
|
|
14
15
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
@@ -351,18 +352,33 @@ const Options = Schema.Struct({
|
|
|
351
352
|
const decodeOptions = ProviderShared.validateWith(Schema.decodeUnknownEffect(Options));
|
|
352
353
|
// Claude on Bedrock requires the thinking budget below `maxTokens`, with a minimum of 1,024.
|
|
353
354
|
const MIN_THINKING_BUDGET = 1_024;
|
|
355
|
+
const isThinkingDisabled = Schema.is(Schema.Struct({
|
|
356
|
+
additionalModelRequestFields: Schema.Struct({ thinking: Schema.Struct({ type: Schema.Literal("disabled") }) }),
|
|
357
|
+
}));
|
|
358
|
+
// Claude 5.1+ binds each thinking signature to the prefix above it. Ask Bedrock to drop the affected blocks instead of
|
|
359
|
+
// failing when that prefix changes. `http.body` overlays this field by field, so callers can still override it.
|
|
360
|
+
const applyThinkingBindingDefault = (request, thinking) => {
|
|
361
|
+
if (isThinkingDisabled(request.http?.body))
|
|
362
|
+
return thinking;
|
|
363
|
+
if (!supportsThinkingBlockBinding(request.model))
|
|
364
|
+
return thinking;
|
|
365
|
+
return {
|
|
366
|
+
...(thinking ?? { type: "adaptive" }),
|
|
367
|
+
block_binding: { prefix_mismatch_behavior: "drop_block" },
|
|
368
|
+
};
|
|
369
|
+
};
|
|
354
370
|
const fromRequest = Effect.fn("BedrockConverse.fromRequest")(function* (request) {
|
|
355
371
|
const toolChoice = request.toolChoice ? yield* lowerToolChoice(request.toolChoice) : undefined;
|
|
356
372
|
const flattened = ProviderShared.flattenToolRequest(request);
|
|
357
373
|
const generation = request.generation;
|
|
358
374
|
const options = yield* decodeOptions(request.providerOptions ?? {});
|
|
359
375
|
const maxTokens = isNova2(request.model) && isHighReasoningEffort(request.http?.body) ? undefined : generation?.maxTokens;
|
|
360
|
-
const thinking = options.thinking === undefined
|
|
376
|
+
const thinking = applyThinkingBindingDefault(request, options.thinking === undefined
|
|
361
377
|
? undefined
|
|
362
378
|
: {
|
|
363
379
|
type: "enabled",
|
|
364
380
|
budget_tokens: ProviderShared.fitThinkingBudget(options.thinking.budgetTokens, maxTokens, MIN_THINKING_BUDGET),
|
|
365
|
-
};
|
|
381
|
+
});
|
|
366
382
|
// Bedrock-Claude shares Anthropic's 4-breakpoint cap. Spend the budget in
|
|
367
383
|
// tools → system → messages order to favour the highest-impact prefixes.
|
|
368
384
|
const breakpoints = BedrockCache.breakpoints(request.model.id);
|
|
@@ -407,6 +423,8 @@ const fromRequest = Effect.fn("BedrockConverse.fromRequest")(function* (request)
|
|
|
407
423
|
: {
|
|
408
424
|
...(generation?.topK === undefined ? {} : { top_k: generation.topK }),
|
|
409
425
|
...(thinking === undefined ? {} : { thinking }),
|
|
426
|
+
// Converse takes Anthropic betas in the body, and Bedrock rejects `block_binding` without this one.
|
|
427
|
+
...(thinking?.block_binding === undefined ? {} : { anthropic_beta: [THINKING_BINDING_BETA] }),
|
|
410
428
|
},
|
|
411
429
|
};
|
|
412
430
|
});
|
|
@@ -478,25 +496,29 @@ const step = (state, event) => Effect.gen(function* () {
|
|
|
478
496
|
const index = event.contentBlockDelta.contentBlockIndex;
|
|
479
497
|
const reasoning = event.contentBlockDelta.delta.reasoningContent;
|
|
480
498
|
const events = [];
|
|
481
|
-
const
|
|
499
|
+
const redactedChunk = yield* (() => {
|
|
482
500
|
if (reasoning.redactedContent === undefined)
|
|
483
501
|
return Effect.succeed(undefined);
|
|
484
|
-
return Effect.fromResult(Encoding.decodeBase64(reasoning.redactedContent)).pipe(Effect.
|
|
502
|
+
return Effect.fromResult(Encoding.decodeBase64(reasoning.redactedContent)).pipe(Effect.mapError((cause) => ProviderShared.eventError(ADAPTER, "Bedrock Converse reasoningContent.redactedContent contains invalid base64 data", undefined, cause)));
|
|
485
503
|
})();
|
|
486
|
-
const
|
|
504
|
+
const redactedChunks = state.reasoningRedactedContent[index] ?? [];
|
|
505
|
+
if (redactedChunk !== undefined)
|
|
506
|
+
redactedChunks.push(redactedChunk);
|
|
487
507
|
const metadata = (() => {
|
|
488
508
|
if (reasoning.signature)
|
|
489
509
|
return providerMetadata(state.providerMetadataKey, { signature: reasoning.signature });
|
|
490
|
-
if (
|
|
491
|
-
return providerMetadata(state.providerMetadataKey, { redactedData });
|
|
510
|
+
if (redactedChunk === undefined && reasoning.data !== undefined)
|
|
511
|
+
return providerMetadata(state.providerMetadataKey, { redactedData: reasoning.data });
|
|
492
512
|
})();
|
|
493
513
|
const lifecycle = (() => {
|
|
494
|
-
if (reasoning.text
|
|
495
|
-
return state.lifecycle;
|
|
496
|
-
|
|
514
|
+
if (reasoning.text !== undefined || metadata !== undefined)
|
|
515
|
+
return Lifecycle.reasoningDelta(state.lifecycle, events, `reasoning-${index}`, reasoning.text ?? "", metadata);
|
|
516
|
+
if (redactedChunk !== undefined)
|
|
517
|
+
return Lifecycle.reasoningStart(state.lifecycle, events, `reasoning-${index}`);
|
|
518
|
+
return state.lifecycle;
|
|
497
519
|
})();
|
|
498
520
|
const reasoningRedactedContent = (() => {
|
|
499
|
-
if (
|
|
521
|
+
if (redactedChunk !== undefined)
|
|
500
522
|
return { ...state.reasoningRedactedContent, [index]: redactedChunks };
|
|
501
523
|
if (reasoning.data === undefined)
|
|
502
524
|
return state.reasoningRedactedContent;
|
|
@@ -609,7 +631,11 @@ const onHalt = (state) => {
|
|
|
609
631
|
return state.finishReason.normalized;
|
|
610
632
|
})();
|
|
611
633
|
const events = [];
|
|
612
|
-
|
|
634
|
+
const lifecycle = Object.entries(state.reasoningRedactedContent).reduce((current, [index, chunks]) => {
|
|
635
|
+
const signature = state.reasoningSignatures[Number(index)];
|
|
636
|
+
return Lifecycle.reasoningEnd(current, events, `reasoning-${index}`, providerMetadata(state.providerMetadataKey, signature ? { signature } : { redactedData: encodeRedactedContent(chunks) }));
|
|
637
|
+
}, state.lifecycle);
|
|
638
|
+
Lifecycle.finish(lifecycle, events, {
|
|
613
639
|
reason: {
|
|
614
640
|
...state.finishReason,
|
|
615
641
|
normalized,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LLMRequest } from "../../schema/index.js";
|
|
2
|
+
export declare const THINKING_BINDING_BETA = "thinking-binding-controls-2026-08-01";
|
|
3
|
+
export declare const claudeVersion: (id: string) => {
|
|
4
|
+
family: string;
|
|
5
|
+
major: number;
|
|
6
|
+
minor: number;
|
|
7
|
+
} | undefined;
|
|
8
|
+
export declare const supportsThinkingBlockBinding: (model: LLMRequest["model"]) => boolean;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const THINKING_BINDING_BETA = "thinking-binding-controls-2026-08-01";
|
|
2
|
+
// Accept gateway namespaces and Vertex suffixes without treating a snapshot date as a minor version.
|
|
3
|
+
export const claudeVersion = (id) => {
|
|
4
|
+
const match = /(?:^|[./])claude-(?<family>[a-z]+)-(?<major>\d+)(?:[.-](?<minor>\d{1,2}))?(?:$|[-:@])/.exec(id.toLowerCase())?.groups;
|
|
5
|
+
if (!match)
|
|
6
|
+
return undefined;
|
|
7
|
+
return { family: match.family, major: Number(match.major), minor: Number(match.minor ?? 0) };
|
|
8
|
+
};
|
|
9
|
+
export const supportsThinkingBlockBinding = (model) => {
|
|
10
|
+
const override = model.compatibility?.supportsThinkingBlockBinding;
|
|
11
|
+
if (override !== undefined)
|
|
12
|
+
return override;
|
|
13
|
+
const version = claudeVersion(model.id);
|
|
14
|
+
return version !== undefined && (version.major > 5 || (version.major === 5 && version.minor >= 1));
|
|
15
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-20276",
|
|
4
4
|
"name": "@opencode/ai",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@clack/prompts": "1.0.0-alpha.1",
|
|
36
36
|
"@effect/platform-node": "4.0.0-rc.112",
|
|
37
|
-
"@opencode/http-recorder": "0.0.0-dev-
|
|
37
|
+
"@opencode/http-recorder": "0.0.0-dev-20276",
|
|
38
38
|
"@tsconfig/bun": "1.0.9",
|
|
39
39
|
"@types/bun": "1.4.0",
|
|
40
40
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@aws-sdk/credential-providers": "3.1057.0",
|
|
45
45
|
"@smithy/eventstream-codec": "4.2.14",
|
|
46
46
|
"@smithy/util-utf8": "4.2.2",
|
|
47
|
-
"@opencode/schema": "0.0.0-dev-
|
|
47
|
+
"@opencode/schema": "0.0.0-dev-20276",
|
|
48
48
|
"aws4fetch": "1.0.20",
|
|
49
49
|
"effect": "4.0.0-rc.112",
|
|
50
50
|
"google-auth-library": "10.5.0"
|