@opencode/ai 0.0.0-dev-20125 → 0.0.0-dev-20127
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.
|
@@ -55,7 +55,10 @@ export const protocol = Protocol.make({
|
|
|
55
55
|
return {
|
|
56
56
|
...(yield* OpenAIChat.protocol.body.from(req)),
|
|
57
57
|
enable_thinking: opts.enableThinking,
|
|
58
|
-
|
|
58
|
+
// Alibaba also rejects an explicit budget that is not below `max_completion_tokens`.
|
|
59
|
+
thinking_budget: opts.thinkingBudget === undefined
|
|
60
|
+
? undefined
|
|
61
|
+
: ProviderShared.fitThinkingBudget(opts.thinkingBudget, req.generation?.maxTokens),
|
|
59
62
|
preserve_thinking: opts.preserveThinking,
|
|
60
63
|
clear_thinking: opts.clearThinking,
|
|
61
64
|
thinking: opts.thinking,
|
|
@@ -22,15 +22,17 @@ export const protocol = Protocol.make({
|
|
|
22
22
|
from: Effect.fn("AlibabaMessages.fromRequest")(function* (req) {
|
|
23
23
|
const opts = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Options))(req.providerOptions ?? {});
|
|
24
24
|
// Model Studio accepts enabled thinking without Anthropic's mandatory token budget.
|
|
25
|
+
const body = yield* AnthropicMessages.protocol.body.from(LLMRequest.update(req, {
|
|
26
|
+
providerOptions: { ...req.providerOptions, thinking: undefined },
|
|
27
|
+
}));
|
|
28
|
+
const budget = opts.thinking?.budgetTokens ?? opts.thinking?.budget_tokens;
|
|
25
29
|
return {
|
|
26
|
-
...
|
|
27
|
-
providerOptions: { ...req.providerOptions, thinking: undefined },
|
|
28
|
-
}))),
|
|
30
|
+
...body,
|
|
29
31
|
thinking: opts.thinking === undefined
|
|
30
32
|
? undefined
|
|
31
33
|
: {
|
|
32
34
|
type: opts.thinking.type,
|
|
33
|
-
budget_tokens:
|
|
35
|
+
budget_tokens: budget === undefined ? undefined : ProviderShared.fitThinkingBudget(budget, body.max_tokens),
|
|
34
36
|
},
|
|
35
37
|
};
|
|
36
38
|
}),
|
|
@@ -20,6 +20,7 @@ const ADAPTER = "anthropic-messages";
|
|
|
20
20
|
export const DEFAULT_BASE_URL = "https://api.anthropic.com/v1";
|
|
21
21
|
export const PATH = "/messages";
|
|
22
22
|
export const DEFAULT_MAX_TOKENS = 32_000;
|
|
23
|
+
const MIN_THINKING_BUDGET = 1_024;
|
|
23
24
|
const DEFAULT_EFFORT = "high";
|
|
24
25
|
const SSE_EVENTS = new Set([
|
|
25
26
|
"message",
|
|
@@ -890,6 +891,13 @@ const applyThinkingBindingDefault = (model, thinking) => {
|
|
|
890
891
|
},
|
|
891
892
|
};
|
|
892
893
|
};
|
|
894
|
+
// Anthropic also requires an explicit thinking budget below `max_tokens` and at or above its minimum.
|
|
895
|
+
const fitThinking = (thinking, maxTokens) => thinking?.type === "enabled"
|
|
896
|
+
? {
|
|
897
|
+
...thinking,
|
|
898
|
+
budget_tokens: ProviderShared.fitThinkingBudget(thinking.budget_tokens, maxTokens, MIN_THINKING_BUDGET),
|
|
899
|
+
}
|
|
900
|
+
: thinking;
|
|
893
901
|
const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (request) {
|
|
894
902
|
const options = yield* decodeOptions(request.providerOptions ?? {});
|
|
895
903
|
const management = options.contextManagement;
|
|
@@ -920,6 +928,7 @@ const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (reques
|
|
|
920
928
|
yield* Effect.logWarning(`Anthropic Messages: dropped ${breakpoints.dropped} cache breakpoint(s); the API allows at most ${ANTHROPIC_BREAKPOINT_CAP} per request.`);
|
|
921
929
|
}
|
|
922
930
|
const output_config = updates.effort === undefined && format === undefined ? undefined : { effort: updates.effort, format };
|
|
931
|
+
const maxTokens = generation?.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
923
932
|
const body = {
|
|
924
933
|
model: request.model.id,
|
|
925
934
|
system,
|
|
@@ -927,12 +936,12 @@ const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (reques
|
|
|
927
936
|
tools,
|
|
928
937
|
tool_choice: toolChoice,
|
|
929
938
|
stream: true,
|
|
930
|
-
max_tokens:
|
|
939
|
+
max_tokens: maxTokens,
|
|
931
940
|
temperature: generation?.temperature,
|
|
932
941
|
top_p: generation?.topP,
|
|
933
942
|
top_k: generation?.topK,
|
|
934
943
|
stop_sequences: generation?.stop,
|
|
935
|
-
thinking: applyThinkingBindingDefault(request.model, options.thinking),
|
|
944
|
+
thinking: applyThinkingBindingDefault(request.model, fitThinking(options.thinking, maxTokens)),
|
|
936
945
|
output_config,
|
|
937
946
|
// top-level passthrough per SDK MessageCreateParamsBase:4638,4643,4649,4654,4670
|
|
938
947
|
cache_control: options.cache_control ?? options.cacheControl,
|
package/dist/protocols/gemini.js
CHANGED
|
@@ -15,6 +15,8 @@ import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
|
15
15
|
const ADAPTER = "gemini";
|
|
16
16
|
// Google documents this sentinel for replaying Gemini 3 function calls after their original signature was lost.
|
|
17
17
|
const SKIP_THOUGHT_SIGNATURE_VALIDATOR = "skip_thought_signature_validator";
|
|
18
|
+
// Gemini 2.5 rejects a budget under the model's minimum: 512 on Flash-Lite, the highest, and 128 on Pro.
|
|
19
|
+
const MIN_THINKING_BUDGET = 512;
|
|
18
20
|
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
19
21
|
// Gemini 3 rejects replayed function calls without a thought signature. Google's SDKs avoid that in normal chats by
|
|
20
22
|
// retaining complete model responses, but OpenCode reconstructs durable history and may encounter an unsigned call
|
|
@@ -366,9 +368,16 @@ const fromRequest = Effect.fn("Gemini.fromRequest")(function* (request) {
|
|
|
366
368
|
presencePenalty: generation?.presencePenalty,
|
|
367
369
|
seed: generation?.seed,
|
|
368
370
|
stopSequences: generation?.stop,
|
|
371
|
+
// Gemini accepts a budget above `maxOutputTokens`, but thinking then leaves the answer empty.
|
|
369
372
|
thinkingConfig: options.thinkingConfig === undefined
|
|
370
373
|
? undefined
|
|
371
|
-
: {
|
|
374
|
+
: {
|
|
375
|
+
...options.thinkingConfig,
|
|
376
|
+
includeThoughts: options.thinkingConfig.includeThoughts ?? true,
|
|
377
|
+
thinkingBudget: options.thinkingConfig.thinkingBudget === undefined
|
|
378
|
+
? undefined
|
|
379
|
+
: ProviderShared.fitThinkingBudget(options.thinkingConfig.thinkingBudget, generation?.maxTokens, MIN_THINKING_BUDGET),
|
|
380
|
+
},
|
|
372
381
|
};
|
|
373
382
|
return {
|
|
374
383
|
cachedContent: options.cachedContent,
|
|
@@ -58,6 +58,12 @@ export declare const subtractTokens: (total: number | undefined, subtrahend: num
|
|
|
58
58
|
* (e.g. Anthropic, whose `input_tokens` is already non-cached only).
|
|
59
59
|
*/
|
|
60
60
|
export declare const sumTokens: (...values: ReadonlyArray<number | undefined>) => number | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Caps an explicit thinking budget at half the output limit. Thinking counts against the output limit, so a budget
|
|
63
|
+
* near it leaves the answer, a tool call, or a summary without room. Smaller budgets, special values such as `-1` and
|
|
64
|
+
* `0`, and requests without an output limit pass through unchanged.
|
|
65
|
+
*/
|
|
66
|
+
export declare const fitThinkingBudget: (budget: number, maxTokens: number | undefined, minimum?: number) => number;
|
|
61
67
|
export declare const eventError: (route: string, message: string, body?: string, cause?: unknown) => AIError;
|
|
62
68
|
export declare const parseJson: (route: string, input: string, message: string) => Effect.Effect<unknown, AIError, never>;
|
|
63
69
|
/**
|
package/dist/protocols/shared.js
CHANGED
|
@@ -75,6 +75,12 @@ export const sumTokens = (...values) => {
|
|
|
75
75
|
return undefined;
|
|
76
76
|
return values.reduce((acc, value) => acc + (value ?? 0), 0);
|
|
77
77
|
};
|
|
78
|
+
/**
|
|
79
|
+
* Caps an explicit thinking budget at half the output limit. Thinking counts against the output limit, so a budget
|
|
80
|
+
* near it leaves the answer, a tool call, or a summary without room. Smaller budgets, special values such as `-1` and
|
|
81
|
+
* `0`, and requests without an output limit pass through unchanged.
|
|
82
|
+
*/
|
|
83
|
+
export const fitThinkingBudget = (budget, maxTokens, minimum = 1) => maxTokens === undefined || budget <= maxTokens / 2 ? budget : Math.max(minimum, Math.floor(maxTokens / 2));
|
|
78
84
|
export const eventError = (route, message, body, cause) => new AIError({
|
|
79
85
|
reason: new InvalidProviderOutputError({ route, message, body, cause }),
|
|
80
86
|
});
|
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-20127",
|
|
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-20127",
|
|
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-20127",
|
|
48
48
|
"aws4fetch": "1.0.20",
|
|
49
49
|
"effect": "4.0.0-rc.112",
|
|
50
50
|
"google-auth-library": "10.5.0"
|