@combycode/llm-sdk 2.2.0 → 2.2.2
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 +48 -0
- package/dist/helpers/one-shot.d.ts +5 -1
- package/dist/index.browser.js +51 -3
- package/dist/index.js +51 -3
- package/dist/llm/providers/anthropic/constants.d.ts +30 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,54 @@ 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.2] — 2026-08-17
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Every Gemini model rejected any tool schema carrying `additionalProperties`.** Function
|
|
12
|
+
declarations were sent on Gemini's `parameters` field, which takes a narrow OpenAPI subset and
|
|
13
|
+
rejects anything outside it outright:
|
|
14
|
+
|
|
15
|
+
> `Invalid JSON payload received. Unknown name "additionalProperties" at 'tools[0].function_declarations[0].parameters'`
|
|
16
|
+
|
|
17
|
+
Since OpenAI strict mode *requires* `additionalProperties: false`, no single tool manifest could
|
|
18
|
+
satisfy both providers — a consuming app had to delete the property, degrading its OpenAI schema,
|
|
19
|
+
to keep Google working. Measured against the live API, the narrow field also rejects `$schema`,
|
|
20
|
+
`$ref`, `$defs`, `definitions`, `const`, `examples`, `exclusiveMinimum`/`Maximum`, `multipleOf`,
|
|
21
|
+
`uniqueItems`, `patternProperties`, `propertyNames`, `if`/`then`, `readOnly`, `deprecated`, and
|
|
22
|
+
`type` given as an array.
|
|
23
|
+
|
|
24
|
+
Schemas now go on `parametersJsonSchema`, which takes full JSON Schema unchanged. Sanitising into
|
|
25
|
+
the subset was the obvious alternative and is worse: it silently drops constraints and cannot
|
|
26
|
+
express a `$ref` at all. Verified end to end on every tool-capable model the catalog ships —
|
|
27
|
+
gemini 2.5 pro/flash/flash-lite, 3-flash, 3.1-pro (incl. customtools), 3.1-flash-lite,
|
|
28
|
+
3.5-flash/-lite, 3.6-flash, gemma-4-26b/-31b: 11/11 fail before the change and drive the tool
|
|
29
|
+
after it. The Interactions surface already accepted full JSON Schema and is unchanged.
|
|
30
|
+
|
|
31
|
+
## [2.2.1] — 2026-08-17
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **Extended thinking returned a 400 on Claude 4.7 and later.** The Anthropic adapter sent
|
|
36
|
+
`thinking: {type:'enabled', budget_tokens: N}` to every model, on the reasoning that it was the
|
|
37
|
+
universally accepted shape — true when written, and since reversed. Anthropic removed
|
|
38
|
+
`budget_tokens` on 4.7+, so Sonnet 5, Opus 5/4.8/4.7 and Fable 5 rejected every thinking request
|
|
39
|
+
outright:
|
|
40
|
+
|
|
41
|
+
> `"thinking.type.enabled" is not supported for this model. Use "thinking.type.adaptive"`
|
|
42
|
+
|
|
43
|
+
There is no shape that works everywhere: Haiku 4.5, Sonnet 4.5 and the Opus 4.x line have no
|
|
44
|
+
adaptive mode at all and still require the budget (`adaptive thinking is not supported on this
|
|
45
|
+
model`), so a blanket switch would have broken the other half. The shape is now chosen per model
|
|
46
|
+
at 4.6 — the version that accepts both — with `effort` mapping to `output_config.effort` on the
|
|
47
|
+
adaptive side instead of a token budget. An unrecognised model id gets `adaptive`, since
|
|
48
|
+
`budget_tokens` is the shape being retired. Both halves verified against the live API.
|
|
49
|
+
|
|
50
|
+
- **`complete()` silently dropped `thinking`.** The one-shot helper never declared the option, so a
|
|
51
|
+
reasoning request through the simplest entry point sent no thinking at all while `client.complete()`
|
|
52
|
+
and agents honoured it. Found while live-testing the fix above — the run came back green because
|
|
53
|
+
nothing was being sent.
|
|
54
|
+
|
|
7
55
|
## [2.2.0] — 2026-08-17
|
|
8
56
|
|
|
9
57
|
### 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 };
|
|
@@ -27080,7 +27103,30 @@ var GoogleAdapter = class {
|
|
|
27080
27103
|
functionDeclarations: fnTools.map((t) => ({
|
|
27081
27104
|
name: t.name,
|
|
27082
27105
|
description: t.description,
|
|
27083
|
-
parameters
|
|
27106
|
+
// `parametersJsonSchema`, NOT `parameters`. The two are mutually exclusive
|
|
27107
|
+
// (sending both is a 400) and accept different things:
|
|
27108
|
+
//
|
|
27109
|
+
// parameters a narrow OpenAPI subset. Anything outside it is
|
|
27110
|
+
// rejected outright with `Unknown name "<keyword>"` —
|
|
27111
|
+
// measured: additionalProperties (at any depth),
|
|
27112
|
+
// $schema, $ref, $defs, definitions, const, examples,
|
|
27113
|
+
// exclusiveMinimum/Maximum, multipleOf, uniqueItems,
|
|
27114
|
+
// patternProperties, propertyNames, if/then,
|
|
27115
|
+
// readOnly, deprecated, and `type` as an array.
|
|
27116
|
+
// parametersJsonSchema full JSON Schema.
|
|
27117
|
+
//
|
|
27118
|
+
// We passed callers' schemas straight into `parameters`, so any tool defined
|
|
27119
|
+
// with `additionalProperties: false` — which OpenAI's strict mode requires —
|
|
27120
|
+
// failed on EVERY Gemini model. A consuming app had to delete it from its
|
|
27121
|
+
// manifest, degrading its OpenAI schema to keep Google working.
|
|
27122
|
+
//
|
|
27123
|
+
// Sanitising into the subset was the obvious fix and is the wrong one: it
|
|
27124
|
+
// silently drops constraints and cannot express a `$ref` at all. Verified on
|
|
27125
|
+
// every tool-capable model we ship (2.5 pro/flash/flash-lite, 3-flash,
|
|
27126
|
+
// 3.1-pro incl. customtools, 3.1-flash-lite, 3.5-flash/-lite, 3.6-flash,
|
|
27127
|
+
// gemma-4-26b/-31b) that this field takes the schema unchanged and the model
|
|
27128
|
+
// still calls the tool.
|
|
27129
|
+
parametersJsonSchema: t.parameters
|
|
27084
27130
|
}))
|
|
27085
27131
|
});
|
|
27086
27132
|
}
|
|
@@ -38859,7 +38905,8 @@ async function complete(opts) {
|
|
|
38859
38905
|
serviceTier,
|
|
38860
38906
|
cache: opts.cache,
|
|
38861
38907
|
topK: opts.topK,
|
|
38862
|
-
seed: opts.seed
|
|
38908
|
+
seed: opts.seed,
|
|
38909
|
+
thinking: opts.thinking
|
|
38863
38910
|
});
|
|
38864
38911
|
} else {
|
|
38865
38912
|
res = await llm.complete(input, {
|
|
@@ -38873,7 +38920,8 @@ async function complete(opts) {
|
|
|
38873
38920
|
serviceTier,
|
|
38874
38921
|
cache: opts.cache,
|
|
38875
38922
|
topK: opts.topK,
|
|
38876
|
-
seed: opts.seed
|
|
38923
|
+
seed: opts.seed,
|
|
38924
|
+
thinking: opts.thinking
|
|
38877
38925
|
});
|
|
38878
38926
|
}
|
|
38879
38927
|
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 };
|
|
@@ -27007,7 +27030,30 @@ var GoogleAdapter = class {
|
|
|
27007
27030
|
functionDeclarations: fnTools.map((t) => ({
|
|
27008
27031
|
name: t.name,
|
|
27009
27032
|
description: t.description,
|
|
27010
|
-
parameters
|
|
27033
|
+
// `parametersJsonSchema`, NOT `parameters`. The two are mutually exclusive
|
|
27034
|
+
// (sending both is a 400) and accept different things:
|
|
27035
|
+
//
|
|
27036
|
+
// parameters a narrow OpenAPI subset. Anything outside it is
|
|
27037
|
+
// rejected outright with `Unknown name "<keyword>"` —
|
|
27038
|
+
// measured: additionalProperties (at any depth),
|
|
27039
|
+
// $schema, $ref, $defs, definitions, const, examples,
|
|
27040
|
+
// exclusiveMinimum/Maximum, multipleOf, uniqueItems,
|
|
27041
|
+
// patternProperties, propertyNames, if/then,
|
|
27042
|
+
// readOnly, deprecated, and `type` as an array.
|
|
27043
|
+
// parametersJsonSchema full JSON Schema.
|
|
27044
|
+
//
|
|
27045
|
+
// We passed callers' schemas straight into `parameters`, so any tool defined
|
|
27046
|
+
// with `additionalProperties: false` — which OpenAI's strict mode requires —
|
|
27047
|
+
// failed on EVERY Gemini model. A consuming app had to delete it from its
|
|
27048
|
+
// manifest, degrading its OpenAI schema to keep Google working.
|
|
27049
|
+
//
|
|
27050
|
+
// Sanitising into the subset was the obvious fix and is the wrong one: it
|
|
27051
|
+
// silently drops constraints and cannot express a `$ref` at all. Verified on
|
|
27052
|
+
// every tool-capable model we ship (2.5 pro/flash/flash-lite, 3-flash,
|
|
27053
|
+
// 3.1-pro incl. customtools, 3.1-flash-lite, 3.5-flash/-lite, 3.6-flash,
|
|
27054
|
+
// gemma-4-26b/-31b) that this field takes the schema unchanged and the model
|
|
27055
|
+
// still calls the tool.
|
|
27056
|
+
parametersJsonSchema: t.parameters
|
|
27011
27057
|
}))
|
|
27012
27058
|
});
|
|
27013
27059
|
}
|
|
@@ -38786,7 +38832,8 @@ async function complete(opts) {
|
|
|
38786
38832
|
serviceTier,
|
|
38787
38833
|
cache: opts.cache,
|
|
38788
38834
|
topK: opts.topK,
|
|
38789
|
-
seed: opts.seed
|
|
38835
|
+
seed: opts.seed,
|
|
38836
|
+
thinking: opts.thinking
|
|
38790
38837
|
});
|
|
38791
38838
|
} else {
|
|
38792
38839
|
res = await llm.complete(input, {
|
|
@@ -38800,7 +38847,8 @@ async function complete(opts) {
|
|
|
38800
38847
|
serviceTier,
|
|
38801
38848
|
cache: opts.cache,
|
|
38802
38849
|
topK: opts.topK,
|
|
38803
|
-
seed: opts.seed
|
|
38850
|
+
seed: opts.seed,
|
|
38851
|
+
thinking: opts.thinking
|
|
38804
38852
|
});
|
|
38805
38853
|
}
|
|
38806
38854
|
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.2",
|
|
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",
|