@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 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;
@@ -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.0",
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",