@combycode/llm-sdk 2.2.0 → 2.2.1
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.
- package/CHANGELOG.md +24 -0
- package/dist/helpers/one-shot.d.ts +5 -1
- package/dist/index.browser.js +27 -2
- package/dist/index.js +27 -2
- package/dist/llm/providers/anthropic/constants.d.ts +30 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@ All notable changes to `@combycode/llm-sdk` are documented here. The format foll
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/) and the project adheres to
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [2.2.1] — 2026-08-17
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Extended thinking returned a 400 on Claude 4.7 and later.** The Anthropic adapter sent
|
|
12
|
+
`thinking: {type:'enabled', budget_tokens: N}` to every model, on the reasoning that it was the
|
|
13
|
+
universally accepted shape — true when written, and since reversed. Anthropic removed
|
|
14
|
+
`budget_tokens` on 4.7+, so Sonnet 5, Opus 5/4.8/4.7 and Fable 5 rejected every thinking request
|
|
15
|
+
outright:
|
|
16
|
+
|
|
17
|
+
> `"thinking.type.enabled" is not supported for this model. Use "thinking.type.adaptive"`
|
|
18
|
+
|
|
19
|
+
There is no shape that works everywhere: Haiku 4.5, Sonnet 4.5 and the Opus 4.x line have no
|
|
20
|
+
adaptive mode at all and still require the budget (`adaptive thinking is not supported on this
|
|
21
|
+
model`), so a blanket switch would have broken the other half. The shape is now chosen per model
|
|
22
|
+
at 4.6 — the version that accepts both — with `effort` mapping to `output_config.effort` on the
|
|
23
|
+
adaptive side instead of a token budget. An unrecognised model id gets `adaptive`, since
|
|
24
|
+
`budget_tokens` is the shape being retired. Both halves verified against the live API.
|
|
25
|
+
|
|
26
|
+
- **`complete()` silently dropped `thinking`.** The one-shot helper never declared the option, so a
|
|
27
|
+
reasoning request through the simplest entry point sent no thinking at all while `client.complete()`
|
|
28
|
+
and agents honoured it. Found while live-testing the fix above — the run came back green because
|
|
29
|
+
nothing was being sent.
|
|
30
|
+
|
|
7
31
|
## [2.2.0] — 2026-08-17
|
|
8
32
|
|
|
9
33
|
### Added
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* LLMClient.complete. Either way the helper destroys its created client
|
|
16
16
|
* before returning so callers don't leak. */
|
|
17
17
|
import type { AgentTool } from '../agent/types';
|
|
18
|
-
import type { CacheConfig } from '../llm/types/request';
|
|
18
|
+
import type { CacheConfig, ThinkingConfig } from '../llm/types/request';
|
|
19
19
|
import type { LLMClientConfig } from '../llm/client-config';
|
|
20
20
|
import type { AudioOptions } from '../llm/types/audio';
|
|
21
21
|
import type { ContentPart, Message } from '../llm/types/messages';
|
|
@@ -67,6 +67,10 @@ export interface CompleteOptions {
|
|
|
67
67
|
/** Which output modalities to return. Default ['text']; add 'audio' for a spoken
|
|
68
68
|
* reply (surfaced as a media part on `response.media`). */
|
|
69
69
|
outputModalities?: Array<'text' | 'audio'>;
|
|
70
|
+
/** Extended thinking. Missing from this helper until 2.2.1: `client.complete()` and
|
|
71
|
+
* agents honoured `thinking` while a one-shot silently dropped it, so the simplest
|
|
72
|
+
* entry point was the only one that could not reason. */
|
|
73
|
+
thinking?: ThinkingConfig;
|
|
70
74
|
/** Service tier for this call. Also settable as a `model:tier` suffix (e.g.
|
|
71
75
|
* `anthropic/claude-opus-4.8:priority`); an explicit value here wins. */
|
|
72
76
|
serviceTier?: ServiceTier;
|
package/dist/index.browser.js
CHANGED
|
@@ -26096,6 +26096,19 @@ var ANTHROPIC_THINKING_BUDGETS = {
|
|
|
26096
26096
|
max: 16384
|
|
26097
26097
|
};
|
|
26098
26098
|
var DEFAULT_ANTHROPIC_THINKING_BUDGET = 2048;
|
|
26099
|
+
var ANTHROPIC_ADAPTIVE_THINKING_MIN = { major: 4, minor: 6 };
|
|
26100
|
+
function anthropicThinkingShape(model) {
|
|
26101
|
+
const id = model.toLowerCase().replace(/^anthropic\//, "");
|
|
26102
|
+
const modern = /^claude-[a-z]+-(\d+)(?:[-.](\d+))?/.exec(id);
|
|
26103
|
+
if (modern) {
|
|
26104
|
+
const major = Number(modern[1]);
|
|
26105
|
+
const minor = modern[2] === void 0 ? 0 : Number(modern[2]);
|
|
26106
|
+
const { major: minMajor, minor: minMinor } = ANTHROPIC_ADAPTIVE_THINKING_MIN;
|
|
26107
|
+
return major > minMajor || major === minMajor && minor >= minMinor ? "adaptive" : "budgeted";
|
|
26108
|
+
}
|
|
26109
|
+
if (/^claude-\d/.test(id)) return "budgeted";
|
|
26110
|
+
return "adaptive";
|
|
26111
|
+
}
|
|
26099
26112
|
var ANTHROPIC_TOP_K_MODELS = /^claude-(opus-4-(1|5|6)|sonnet-4-(5|6)|haiku-4-5)(\b|-)/;
|
|
26100
26113
|
function anthropicAcceptsTopK(model) {
|
|
26101
26114
|
return ANTHROPIC_TOP_K_MODELS.test(model);
|
|
@@ -26477,6 +26490,16 @@ var AnthropicAdapter = class {
|
|
|
26477
26490
|
}
|
|
26478
26491
|
if (req.thinking) {
|
|
26479
26492
|
if (req.thinking.mode === "off") {
|
|
26493
|
+
} else if (anthropicThinkingShape(req.model) === "adaptive") {
|
|
26494
|
+
const thinking = { type: "adaptive" };
|
|
26495
|
+
if (req.thinking.visibility === "hidden") thinking.display = "omitted";
|
|
26496
|
+
body.thinking = thinking;
|
|
26497
|
+
if (req.thinking.effort) {
|
|
26498
|
+
body.output_config = {
|
|
26499
|
+
...body.output_config ?? {},
|
|
26500
|
+
effort: req.thinking.effort
|
|
26501
|
+
};
|
|
26502
|
+
}
|
|
26480
26503
|
} else {
|
|
26481
26504
|
const budget = req.thinking.effort ? ANTHROPIC_THINKING_BUDGETS[req.thinking.effort] ?? DEFAULT_ANTHROPIC_THINKING_BUDGET : DEFAULT_ANTHROPIC_THINKING_BUDGET;
|
|
26482
26505
|
const thinking = { type: "enabled", budget_tokens: budget };
|
|
@@ -38859,7 +38882,8 @@ async function complete(opts) {
|
|
|
38859
38882
|
serviceTier,
|
|
38860
38883
|
cache: opts.cache,
|
|
38861
38884
|
topK: opts.topK,
|
|
38862
|
-
seed: opts.seed
|
|
38885
|
+
seed: opts.seed,
|
|
38886
|
+
thinking: opts.thinking
|
|
38863
38887
|
});
|
|
38864
38888
|
} else {
|
|
38865
38889
|
res = await llm.complete(input, {
|
|
@@ -38873,7 +38897,8 @@ async function complete(opts) {
|
|
|
38873
38897
|
serviceTier,
|
|
38874
38898
|
cache: opts.cache,
|
|
38875
38899
|
topK: opts.topK,
|
|
38876
|
-
seed: opts.seed
|
|
38900
|
+
seed: opts.seed,
|
|
38901
|
+
thinking: opts.thinking
|
|
38877
38902
|
});
|
|
38878
38903
|
}
|
|
38879
38904
|
const result = {
|
package/dist/index.js
CHANGED
|
@@ -26023,6 +26023,19 @@ var ANTHROPIC_THINKING_BUDGETS = {
|
|
|
26023
26023
|
max: 16384
|
|
26024
26024
|
};
|
|
26025
26025
|
var DEFAULT_ANTHROPIC_THINKING_BUDGET = 2048;
|
|
26026
|
+
var ANTHROPIC_ADAPTIVE_THINKING_MIN = { major: 4, minor: 6 };
|
|
26027
|
+
function anthropicThinkingShape(model) {
|
|
26028
|
+
const id = model.toLowerCase().replace(/^anthropic\//, "");
|
|
26029
|
+
const modern = /^claude-[a-z]+-(\d+)(?:[-.](\d+))?/.exec(id);
|
|
26030
|
+
if (modern) {
|
|
26031
|
+
const major = Number(modern[1]);
|
|
26032
|
+
const minor = modern[2] === void 0 ? 0 : Number(modern[2]);
|
|
26033
|
+
const { major: minMajor, minor: minMinor } = ANTHROPIC_ADAPTIVE_THINKING_MIN;
|
|
26034
|
+
return major > minMajor || major === minMajor && minor >= minMinor ? "adaptive" : "budgeted";
|
|
26035
|
+
}
|
|
26036
|
+
if (/^claude-\d/.test(id)) return "budgeted";
|
|
26037
|
+
return "adaptive";
|
|
26038
|
+
}
|
|
26026
26039
|
var ANTHROPIC_TOP_K_MODELS = /^claude-(opus-4-(1|5|6)|sonnet-4-(5|6)|haiku-4-5)(\b|-)/;
|
|
26027
26040
|
function anthropicAcceptsTopK(model) {
|
|
26028
26041
|
return ANTHROPIC_TOP_K_MODELS.test(model);
|
|
@@ -26404,6 +26417,16 @@ var AnthropicAdapter = class {
|
|
|
26404
26417
|
}
|
|
26405
26418
|
if (req.thinking) {
|
|
26406
26419
|
if (req.thinking.mode === "off") {
|
|
26420
|
+
} else if (anthropicThinkingShape(req.model) === "adaptive") {
|
|
26421
|
+
const thinking = { type: "adaptive" };
|
|
26422
|
+
if (req.thinking.visibility === "hidden") thinking.display = "omitted";
|
|
26423
|
+
body.thinking = thinking;
|
|
26424
|
+
if (req.thinking.effort) {
|
|
26425
|
+
body.output_config = {
|
|
26426
|
+
...body.output_config ?? {},
|
|
26427
|
+
effort: req.thinking.effort
|
|
26428
|
+
};
|
|
26429
|
+
}
|
|
26407
26430
|
} else {
|
|
26408
26431
|
const budget = req.thinking.effort ? ANTHROPIC_THINKING_BUDGETS[req.thinking.effort] ?? DEFAULT_ANTHROPIC_THINKING_BUDGET : DEFAULT_ANTHROPIC_THINKING_BUDGET;
|
|
26409
26432
|
const thinking = { type: "enabled", budget_tokens: budget };
|
|
@@ -38786,7 +38809,8 @@ async function complete(opts) {
|
|
|
38786
38809
|
serviceTier,
|
|
38787
38810
|
cache: opts.cache,
|
|
38788
38811
|
topK: opts.topK,
|
|
38789
|
-
seed: opts.seed
|
|
38812
|
+
seed: opts.seed,
|
|
38813
|
+
thinking: opts.thinking
|
|
38790
38814
|
});
|
|
38791
38815
|
} else {
|
|
38792
38816
|
res = await llm.complete(input, {
|
|
@@ -38800,7 +38824,8 @@ async function complete(opts) {
|
|
|
38800
38824
|
serviceTier,
|
|
38801
38825
|
cache: opts.cache,
|
|
38802
38826
|
topK: opts.topK,
|
|
38803
|
-
seed: opts.seed
|
|
38827
|
+
seed: opts.seed,
|
|
38828
|
+
thinking: opts.thinking
|
|
38804
38829
|
});
|
|
38805
38830
|
}
|
|
38806
38831
|
const result = {
|
|
@@ -11,5 +11,35 @@ export declare const ANTHROPIC_THINKING_BUDGETS: Record<string, number>;
|
|
|
11
11
|
* unrecognised.
|
|
12
12
|
*/
|
|
13
13
|
export declare const DEFAULT_ANTHROPIC_THINKING_BUDGET = 2048;
|
|
14
|
+
/**
|
|
15
|
+
* The version at which `thinking: {type:'adaptive'}` takes over from
|
|
16
|
+
* `{type:'enabled', budget_tokens}`.
|
|
17
|
+
*
|
|
18
|
+
* There is no shape that works everywhere, and the direction reversed under us. This
|
|
19
|
+
* adapter used to send the budgeted form to every model, on the reasoning that it was the
|
|
20
|
+
* universally accepted one — true when it was written. Anthropic then REMOVED
|
|
21
|
+
* `budget_tokens` on 4.7 and later: Sonnet 5, Opus 5/4.8/4.7 and Fable 5 reject it with a
|
|
22
|
+
* 400 ("thinking.type.enabled is not supported for this model"). Meanwhile the older half
|
|
23
|
+
* — Haiku 4.5, Sonnet 4.5, Opus 4.x — has no `adaptive` at all and still requires the
|
|
24
|
+
* budget. So the shape must be chosen per model.
|
|
25
|
+
*
|
|
26
|
+
* 4.6 is the boundary: it accepts both and prefers `adaptive`, everything above requires
|
|
27
|
+
* `adaptive`, everything below requires the budget.
|
|
28
|
+
*/
|
|
29
|
+
export declare const ANTHROPIC_ADAPTIVE_THINKING_MIN: {
|
|
30
|
+
readonly major: 4;
|
|
31
|
+
readonly minor: 6;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Pick the `thinking` shape for a model id.
|
|
35
|
+
*
|
|
36
|
+
* Parsed from the id rather than read from the catalog on purpose: the catalog is optional
|
|
37
|
+
* (an engine can run with none), `buildRequest` has no access to it, and its per-model
|
|
38
|
+
* `reasoning` block does not currently distinguish the two shapes anyway.
|
|
39
|
+
*
|
|
40
|
+
* An unrecognised id gets `adaptive`, because `budget_tokens` is the shape being retired —
|
|
41
|
+
* an id we do not recognise is far likelier to be newer than us than older.
|
|
42
|
+
*/
|
|
43
|
+
export declare function anthropicThinkingShape(model: string): 'adaptive' | 'budgeted';
|
|
14
44
|
/** True when this Anthropic model still accepts `top_k` (see ANTHROPIC_TOP_K_MODELS). */
|
|
15
45
|
export declare function anthropicAcceptsTopK(model: string): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@combycode/llm-sdk",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "Unified, pluggable AI SDK for accessing the LLMs of every major provider (Anthropic, OpenAI, Google, xAI, OpenRouter) through one API. Cross-environment: Node, Bun, and the browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|