@warlock.js/ai-anthropic 5.29.0 → 5.31.0
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 +63 -0
- package/cjs/index.cjs +1124 -147
- package/cjs/index.cjs.map +1 -1
- package/esm/config.type.d.mts +170 -3
- package/esm/config.type.d.mts.map +1 -1
- package/esm/index.d.mts +9 -3
- package/esm/index.mjs +8 -1
- package/esm/known-output-limits.mjs +46 -0
- package/esm/known-output-limits.mjs.map +1 -0
- package/esm/known-pricing.d.mts +58 -0
- package/esm/known-pricing.d.mts.map +1 -0
- package/esm/known-pricing.mjs +165 -0
- package/esm/known-pricing.mjs.map +1 -0
- package/esm/known-vision-models.mjs +1 -1
- package/esm/known-vision-models.mjs.map +1 -1
- package/esm/model.d.mts +322 -0
- package/esm/model.d.mts.map +1 -0
- package/esm/model.mjs +523 -73
- package/esm/model.mjs.map +1 -1
- package/esm/request.type.d.mts +25 -0
- package/esm/request.type.d.mts.map +1 -0
- package/esm/sdk.d.mts +7 -6
- package/esm/sdk.d.mts.map +1 -1
- package/esm/sdk.mjs +4 -4
- package/esm/sdk.mjs.map +1 -1
- package/esm/thinking-mode.d.mts +15 -2
- package/esm/thinking-mode.d.mts.map +1 -1
- package/esm/thinking-mode.mjs +128 -2
- package/esm/thinking-mode.mjs.map +1 -1
- package/esm/utils/cache-usage.mjs +62 -0
- package/esm/utils/cache-usage.mjs.map +1 -0
- package/esm/utils/index.d.mts +3 -0
- package/esm/utils/index.mjs +1 -0
- package/esm/utils/map-stop-reason.d.mts +1 -0
- package/esm/utils/map-stop-reason.mjs +17 -13
- package/esm/utils/map-stop-reason.mjs.map +1 -1
- package/esm/utils/provider-blocks.d.mts +39 -0
- package/esm/utils/provider-blocks.d.mts.map +1 -0
- package/esm/utils/provider-blocks.mjs +83 -0
- package/esm/utils/provider-blocks.mjs.map +1 -0
- package/esm/utils/to-anthropic-messages.d.mts +71 -0
- package/esm/utils/to-anthropic-messages.d.mts.map +1 -0
- package/esm/utils/to-anthropic-messages.mjs +53 -13
- package/esm/utils/to-anthropic-messages.mjs.map +1 -1
- package/esm/utils/to-anthropic-tools.d.mts +19 -0
- package/esm/utils/to-anthropic-tools.d.mts.map +1 -0
- package/esm/utils/to-anthropic-tools.mjs +1 -1
- package/esm/utils/to-anthropic-tools.mjs.map +1 -1
- package/esm/utils/wrap-anthropic-error.d.mts +1 -0
- package/esm/utils/wrap-anthropic-error.mjs +7 -2
- package/esm/utils/wrap-anthropic-error.mjs.map +1 -1
- package/llms-full.txt +273 -27
- package/llms.txt +1 -1
- package/package.json +5 -4
- package/skills/README.md +1 -1
- package/skills/setup-anthropic/SKILL.md +273 -27
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thinking-mode.mjs","names":[],"sources":["../../../../../../ai-anthropic/src/thinking-mode.ts"],"sourcesContent":["/**\n * How a Claude model accepts extended thinking on the Messages API.\n *\n * - `\"adaptive\"` — `thinking: { type: \"adaptive\" }`, with depth steered by\n * `output_config.effort`. Sending `budget_tokens` to these models is a\n * 400. Installed SDK: `ThinkingConfigAdaptive`\n * (`@anthropic-ai/sdk/resources/messages/messages.d.ts:1065`) and\n * `OutputConfig.effort` (`messages.d.ts:796-800`).\n * - `\"budget\"` — the legacy `thinking: { type: \"enabled\", budget_tokens }`\n * (`ThinkingConfigEnabled`, `messages.d.ts:1078-1090`). Required on\n * Haiku 4.5 and older models.\n */\nexport type AnthropicThinkingMode = \"adaptive\" | \"budget\";\n\n/*\n * Model-family table (source: claude-api ref, \"Thinking & Effort\" per-model\n * table, cached 2026-09-25; summarised in `releases/5.26-ai-sdk-review.md` §A):\n *\n * | Model ids | Mode | `budget_tokens` | `temperature` |\n * |----------------------------------------------|------------|-------------------------|--------------------|\n * | claude-fable-5, claude-fable-5-1 (+ mythos) | adaptive | removed (400) | removed (400) |\n * | claude-opus-5-5, claude-opus-5 | adaptive | removed (400) | removed (400) |\n * | claude-opus-4-8, claude-opus-4-7 | adaptive | removed (400) | removed (400) |\n * | claude-sonnet-5-5, claude-sonnet-5 | adaptive | removed (400) | 400 (5.5: non-1.0) |\n * | claude-opus-4-6, claude-sonnet-4-6 | budget (*) | deprecated, still works | allowed |\n * | claude-haiku-4-5 and every older model | budget | required for thinking | allowed |\n * | anything not listed (future / unknown ids) | adaptive | — | — |\n *\n * (*) The 4.6 pair accepts both shapes; the claude-api ref recommends\n * adaptive there but keeps `budget_tokens` functional as a transitional\n * path. It stays on `budget` so 4.6 callers see no behaviour change (their\n * `reasoning.maxTokens` is still honoured as a hard budget) and so that\n * the `budget` set is exactly the set that still accepts `temperature`\n * (see `acceptsTemperature`). Opt 4.6 into adaptive with\n * `anthropic.model({ name, thinkingMode: \"adaptive\" })`.\n *\n * Unknown ids default to adaptive because that is the current API\n * direction: every model released after Opus 4.6 rejects `budget_tokens`.\n */\n\n/**\n * Family prefixes that are budget-mode in their entirety — plain\n * `startsWith` match (dated and `-latest` variants included).\n */\nconst BUDGET_FAMILY_PREFIXES = [\n // Claude 3 / 3.5 / 3.7 legacy naming (claude-3-haiku-…, claude-3-5-sonnet-…).\n \"claude-3\",\n // Pre-3 text models: no thinking support at all, never adaptive.\n \"claude-2\",\n \"claude-instant\",\n // Haiku 4.x: Haiku 4.5 still requires budget_tokens (claude-api ref).\n \"claude-haiku-4\",\n];\n\n/**\n * Individual pre-4.7 releases in the `claude-<tier>-4-*` naming. Matched\n * as a release root: the id must equal the root or continue with a\n * separator that is not a new minor version number, so `claude-opus-4`\n * matches `claude-opus-4-20250514` but NOT `claude-opus-4-7`. Ids from the\n * installed SDK `Model` union (`messages.d.ts:795`) plus the 4.6 pair.\n */\nconst BUDGET_RELEASE_ROOTS = [\n \"claude-opus-4\",\n \"claude-opus-4-0\",\n \"claude-opus-4-1\",\n \"claude-opus-4-5\",\n \"claude-opus-4-6\",\n \"claude-sonnet-4\",\n \"claude-sonnet-4-0\",\n \"claude-sonnet-4-5\",\n \"claude-sonnet-4-6\",\n];\n\n/**\n * A remainder that starts a new minor version (`-7`, `-10`) — used to\n * stop a release root from swallowing a later release of the same line.\n * Dates (`-20250514`) and tags (`-latest`, `-v1`) are not minor versions.\n */\nconst NEXT_MINOR_VERSION = /^-\\d{1,2}(?!\\d)/;\n\nfunction matchesReleaseRoot(name: string, root: string): boolean {\n if (!name.startsWith(root)) {\n return false;\n }\n\n const rest = name.slice(root.length);\n\n if (rest === \"\") {\n return true;\n }\n\n return (rest.startsWith(\"-\") || rest.startsWith(\"@\")) && !NEXT_MINOR_VERSION.test(rest);\n}\n\n/**\n * Infer the thinking mode for a Claude model id. Budget for Haiku 4.5, the\n * 4.6 pair, and every older family; adaptive for everything else,\n * including unknown and future ids. Explicit per-model config\n * (`AnthropicModelConfig.thinkingMode`) always wins over this inference.\n *\n * @example\n * inferThinkingMode(\"claude-opus-5-5\"); // → \"adaptive\"\n * inferThinkingMode(\"claude-haiku-4-5\"); // → \"budget\"\n * inferThinkingMode(\"claude-opus-9\"); // → \"adaptive\" (unknown → current API direction)\n */\nexport function inferThinkingMode(modelName: string): AnthropicThinkingMode {\n const normalized = modelName.toLowerCase();\n\n if (BUDGET_FAMILY_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {\n return \"budget\";\n }\n\n if (BUDGET_RELEASE_ROOTS.some((root) => matchesReleaseRoot(normalized, root))) {\n return \"budget\";\n }\n\n return \"adaptive\";\n}\n\n/**\n * Whether a model of this thinking mode accepts an explicit `temperature`.\n *\n * Installed SDK, `MessageCreateParamsBase.temperature`\n * (`messages.d.ts:2036-2040`): \"Models released after Claude Opus 4.6 do\n * not support setting temperature. A value of 1.0 will be accepted for\n * backwards compatibility, all other values will be rejected with a 400\n * error.\" The claude-api ref's per-model table agrees: sampling is\n * \"Removed - 400\" on Fable 5/5.1, Opus 5.5/5/4.8/4.7 and Sonnet 5, and\n * \"Non-default values - 400\" on Sonnet 5.5; \"Allowed\" on Opus 4.6,\n * Sonnet 4.6, Haiku 4.5 and older.\n *\n * That split is exactly the adaptive/budget split in the table above\n * (adaptive = released after Opus 4.6; budget = Opus 4.6 and earlier), so\n * the thinking mode doubles as the sampling-parameter classification.\n */\nexport function acceptsTemperature(mode: AnthropicThinkingMode): boolean {\n return mode === \"budget\";\n}\n"],"mappings":";;;;;AA4CA,MAAM,yBAAyB;CAE7B;CAEA;CACA;CAEA;AACF;;;;;;;;AASA,MAAM,uBAAuB;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;AAOA,MAAM,qBAAqB;AAE3B,SAAS,mBAAmB,MAAc,MAAuB;CAC/D,IAAI,CAAC,KAAK,WAAW,IAAI,GACvB,OAAO;CAGT,MAAM,OAAO,KAAK,MAAM,KAAK,MAAM;CAEnC,IAAI,SAAS,IACX,OAAO;CAGT,QAAQ,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,GAAG,MAAM,CAAC,mBAAmB,KAAK,IAAI;AACxF;;;;;;;;;;;;AAaA,SAAgB,kBAAkB,WAA0C;CAC1E,MAAM,aAAa,UAAU,YAAY;CAEzC,IAAI,uBAAuB,MAAM,WAAW,WAAW,WAAW,MAAM,CAAC,GACvE,OAAO;CAGT,IAAI,qBAAqB,MAAM,SAAS,mBAAmB,YAAY,IAAI,CAAC,GAC1E,OAAO;CAGT,OAAO;AACT;;;;;;;;;;;;;;;;;AAkBA,SAAgB,mBAAmB,MAAsC;CACvE,OAAO,SAAS;AAClB"}
|
|
1
|
+
{"version":3,"file":"thinking-mode.mjs","names":[],"sources":["../../../../../../../packages/ai-anthropic/src/thinking-mode.ts"],"sourcesContent":["/**\n * How a Claude model accepts extended thinking on the Messages API.\n *\n * - `\"adaptive\"` — `thinking: { type: \"adaptive\" }`, with depth steered by\n * `output_config.effort`. Sending `budget_tokens` to these models is a\n * 400. Installed SDK: `ThinkingConfigAdaptive`\n * (`@anthropic-ai/sdk/resources/messages/messages.d.ts:1065`) and\n * `OutputConfig.effort` (`messages.d.ts:796-800`).\n * - `\"budget\"` — the legacy `thinking: { type: \"enabled\", budget_tokens }`\n * (`ThinkingConfigEnabled`, `messages.d.ts:1078-1090`). Required on\n * Haiku 4.5 and older models.\n */\nexport type AnthropicThinkingMode = \"adaptive\" | \"budget\";\n\n/*\n * Model-family table (source: claude-api ref, \"Thinking & Effort\" per-model\n * table, cached 2026-09-25; summarised in `releases/5.26-ai-sdk-review.md` §A):\n *\n * | Model ids | Mode | `budget_tokens` | `temperature` |\n * |----------------------------------------------|------------|-------------------------|--------------------|\n * | claude-fable-5, claude-fable-5-1 (+ mythos) | adaptive | removed (400) | removed (400) |\n * | claude-opus-5-5, claude-opus-5 | adaptive | removed (400) | removed (400) |\n * | claude-opus-4-8, claude-opus-4-7 | adaptive | removed (400) | removed (400) |\n * | claude-sonnet-5-5, claude-sonnet-5 | adaptive | removed (400) | 400 (5.5: non-1.0) |\n * | claude-opus-4-6, claude-sonnet-4-6 | budget (*) | deprecated, still works | allowed |\n * | claude-haiku-4-5 and every older model | budget | required for thinking | allowed |\n * | anything not listed (future / unknown ids) | adaptive | — | — |\n *\n * (*) The 4.6 pair accepts both shapes; the claude-api ref recommends\n * adaptive there but keeps `budget_tokens` functional as a transitional\n * path. It stays on `budget` so 4.6 callers see no behaviour change (their\n * `reasoning.maxTokens` is still honoured as a hard budget) and so that\n * the `budget` set is exactly the set that still accepts `temperature`\n * (see `acceptsTemperature`). Opt 4.6 into adaptive with\n * `anthropic.model({ name, thinkingMode: \"adaptive\" })`.\n *\n * Unknown ids default to adaptive because that is the current API\n * direction: every model released after Opus 4.6 rejects `budget_tokens`.\n */\n\n/**\n * Family prefixes that are budget-mode in their entirety — plain\n * `startsWith` match (dated and `-latest` variants included).\n */\nconst BUDGET_FAMILY_PREFIXES = [\n // Claude 3 / 3.5 / 3.7 legacy naming (claude-3-haiku-…, claude-3-5-sonnet-…).\n \"claude-3\",\n // Pre-3 text models: no thinking support at all, never adaptive.\n \"claude-2\",\n \"claude-instant\",\n // Haiku 4.x: Haiku 4.5 still requires budget_tokens (claude-api ref).\n \"claude-haiku-4\",\n];\n\n/**\n * Individual pre-4.7 releases in the `claude-<tier>-4-*` naming. Matched\n * as a release root: the id must equal the root or continue with a\n * separator that is not a new minor version number, so `claude-opus-4`\n * matches `claude-opus-4-20250514` but NOT `claude-opus-4-7`. Ids from the\n * installed SDK `Model` union (`messages.d.ts:795`) plus the 4.6 pair.\n */\nconst BUDGET_RELEASE_ROOTS = [\n \"claude-opus-4\",\n \"claude-opus-4-0\",\n \"claude-opus-4-1\",\n \"claude-opus-4-5\",\n \"claude-opus-4-6\",\n \"claude-sonnet-4\",\n \"claude-sonnet-4-0\",\n \"claude-sonnet-4-5\",\n \"claude-sonnet-4-6\",\n];\n\n/**\n * A remainder that starts a new minor version (`-7`, `-10`) — used to\n * stop a release root from swallowing a later release of the same line.\n * Dates (`-20250514`) and tags (`-latest`, `-v1`) are not minor versions.\n */\nconst NEXT_MINOR_VERSION = /^-\\d{1,2}(?!\\d)/;\n\n/**\n * Whether a lower-cased model id is the release `root` itself, or a dated /\n * tagged / `@` snapshot of it — but not a later minor version of the same\n * line (`claude-opus-4` matches `claude-opus-4-20250514`, not `claude-opus-4-7`).\n */\nexport function matchesReleaseRoot(name: string, root: string): boolean {\n if (!name.startsWith(root)) {\n return false;\n }\n\n const rest = name.slice(root.length);\n\n if (rest === \"\") {\n return true;\n }\n\n return (rest.startsWith(\"-\") || rest.startsWith(\"@\")) && !NEXT_MINOR_VERSION.test(rest);\n}\n\n/**\n * Infer the thinking mode for a Claude model id. Budget for Haiku 4.5, the\n * 4.6 pair, and every older family; adaptive for everything else,\n * including unknown and future ids. Explicit per-model config\n * (`AnthropicModelConfig.thinkingMode`) always wins over this inference.\n *\n * @example\n * inferThinkingMode(\"claude-opus-5-5\"); // → \"adaptive\"\n * inferThinkingMode(\"claude-haiku-4-5\"); // → \"budget\"\n * inferThinkingMode(\"claude-opus-9\"); // → \"adaptive\" (unknown → current API direction)\n */\nexport function inferThinkingMode(modelName: string): AnthropicThinkingMode {\n const normalized = modelName.toLowerCase();\n\n if (BUDGET_FAMILY_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {\n return \"budget\";\n }\n\n if (BUDGET_RELEASE_ROOTS.some((root) => matchesReleaseRoot(normalized, root))) {\n return \"budget\";\n }\n\n return \"adaptive\";\n}\n\n/**\n * Whether a model of this thinking mode accepts an explicit `temperature`.\n *\n * Installed SDK, `MessageCreateParamsBase.temperature`\n * (`messages.d.ts:2036-2040`): \"Models released after Claude Opus 4.6 do\n * not support setting temperature. A value of 1.0 will be accepted for\n * backwards compatibility, all other values will be rejected with a 400\n * error.\" The claude-api ref's per-model table agrees: sampling is\n * \"Removed - 400\" on Fable 5/5.1, Opus 5.5/5/4.8/4.7 and Sonnet 5, and\n * \"Non-default values - 400\" on Sonnet 5.5; \"Allowed\" on Opus 4.6,\n * Sonnet 4.6, Haiku 4.5 and older.\n *\n * That split is exactly the adaptive/budget split in the table above\n * (adaptive = released after Opus 4.6; budget = Opus 4.6 and earlier), so\n * the thinking mode doubles as the sampling-parameter classification.\n */\nexport function acceptsTemperature(mode: AnthropicThinkingMode): boolean {\n return mode === \"budget\";\n}\n\n/**\n * Model release roots that accept `thinking: { type: \"between_tools\" }`,\n * the only way to turn thinking off on them. Source: claude-api ref,\n * \"Thinking & Effort\" per-model table (cached 2026-10-06): Claude Sonnet\n * 5.5 returns a 400 on `{ type: \"disabled\" }` and takes `between_tools`\n * instead (no other field alongside it, effort `high` or below). No other\n * current model is documented to accept it. Opus 5.5 and Fable 5/5.1 have\n * no thinking-off form at all: `disabled` and `budget_tokens` are a 400 at\n * every effort, so the lowest they go is `effort: \"low\"`.\n */\nconst BETWEEN_TOOLS_RELEASE_ROOTS = [\"claude-sonnet-5-5\"];\n\n/**\n * Whether `effort: \"none\"` can turn thinking off on this model with\n * `thinking: { type: \"between_tools\" }`. Matches the release id with or\n * without a platform prefix (`anthropic.claude-sonnet-5-5` on Bedrock)\n * and with dated or tagged suffixes, but not a later minor release.\n *\n * @example\n * acceptsBetweenTools(\"claude-sonnet-5-5\"); // → true\n * acceptsBetweenTools(\"claude-opus-5-5\"); // → false\n */\nexport function acceptsBetweenTools(modelName: string): boolean {\n return matchesAnyRelease(modelName, BETWEEN_TOOLS_RELEASE_ROOTS);\n}\n\n/**\n * Whether the model id — lower-cased, with any platform prefix\n * (`anthropic.`, `us.anthropic.` on Bedrock) removed — is one of these\n * releases, dated or tagged, but not a later minor release.\n */\nfunction matchesAnyRelease(modelName: string, roots: readonly string[]): boolean {\n const normalized = modelName.toLowerCase();\n const id = normalized.slice(normalized.lastIndexOf(\".\") + 1);\n\n return roots.some((root) => matchesReleaseRoot(id, root));\n}\n\n/**\n * How a model is asked to run without reasoning when the caller passes the\n * neutral `reasoning: { effort: \"none\" }`.\n *\n * - `\"between_tools\"` — send `thinking: { type: \"between_tools\" }` alone.\n * - `\"disabled\"` — send `thinking: { type: \"disabled\" }` with no effort.\n * - `\"lowest-effort\"` — keep adaptive thinking on and send the lowest\n * `output_config.effort` (`\"low\"`).\n * - `\"omit\"` — send no `thinking` field at all.\n */\nexport type AnthropicNoReasoningForm = \"between_tools\" | \"disabled\" | \"lowest-effort\" | \"omit\";\n\n/**\n * Releases that accept `thinking: { type: \"disabled\" }` and run reliably\n * with it. Source: claude-api ref, \"Thinking & Effort\" per-model table\n * (cached 2026-10-06): Claude Sonnet 5 accepts `disabled`; Claude Haiku 5.5\n * accepts it at effort `low`, `medium` and `high` (a 400 at `xhigh` /\n * `max`). No effort is sent alongside it, so Haiku 5.5 runs at its default,\n * `medium`, which is inside the accepted range.\n */\nconst DISABLED_THINKING_RELEASE_ROOTS = [\"claude-sonnet-5\", \"claude-haiku-5-5\"];\n\n/**\n * Releases that accept `disabled` but are kept on adaptive thinking at the\n * lowest effort instead. Claude Opus 5 accepts `disabled` at effort `high`\n * or below, but the claude-api ref (\"Two failure modes when thinking is\n * disabled\", cached 2026-10-06) documents that with thinking disabled it\n * occasionally writes a tool call into its visible text instead of a\n * `tool_use` block — the call never runs and no error is raised — and can\n * leak `<thinking>` tags into the response. The ref's primary\n * recommendation is to keep thinking on and lower `effort`, so the adapter\n * does exactly that.\n */\nconst LOWEST_EFFORT_RELEASE_ROOTS = [\"claude-opus-5\"];\n\n/**\n * The wire form for `reasoning: { effort: \"none\" }` on this model:\n *\n * | Model | Form |\n * |----------------------------------------|-------------------|\n * | Claude Sonnet 5.5 | `between_tools` |\n * | Claude Sonnet 5, Claude Haiku 5.5 | `disabled` |\n * | Claude Opus 5 | `lowest-effort` |\n * | Opus 5.5, Fable 5/5.1, everything else | `omit` |\n *\n * Opus 5.5 and Fable 5/5.1 have no thinking-off form (`disabled` and\n * `budget_tokens` are a 400 at every effort), so nothing is sent and the\n * model runs at its default effort; older models run without thinking when\n * the field is absent.\n *\n * @example\n * noReasoningForm(\"claude-sonnet-5\"); // → \"disabled\"\n * noReasoningForm(\"claude-opus-5\"); // → \"lowest-effort\"\n * noReasoningForm(\"claude-opus-5-5\"); // → \"omit\"\n */\nexport function noReasoningForm(modelName: string): AnthropicNoReasoningForm {\n if (acceptsBetweenTools(modelName)) {\n return \"between_tools\";\n }\n\n if (matchesAnyRelease(modelName, DISABLED_THINKING_RELEASE_ROOTS)) {\n return \"disabled\";\n }\n\n if (matchesAnyRelease(modelName, LOWEST_EFFORT_RELEASE_ROOTS)) {\n return \"lowest-effort\";\n }\n\n return \"omit\";\n}\n\n/*\n * Preserved thinking (Anthropic docs, \"Thinking\" → \"Preserved thinking\" and\n * the \"Preserved thinking\" page, read 2026-10-17): the API checks that the\n * `system` prompt, `tools` and every message before a replayed thinking\n * block are unchanged since the block was produced, on Claude Fable 5.1,\n * Claude Opus 5.5, Claude Sonnet 5.5 and Claude Haiku 5.5. Claude Mythos 5.1\n * and every model released before Claude Fable 5.1 don't run that check, so\n * a thinking block kept after a client-written summary stays valid there.\n */\nconst PREFIX_CHECK_RELEASE_ROOTS = [\n \"claude-fable-5-1\",\n \"claude-opus-5-5\",\n \"claude-sonnet-5-5\",\n \"claude-haiku-5-5\",\n];\n\n/** Releases documented not to run the conversation check. */\nconst NO_PREFIX_CHECK_RELEASE_ROOTS = [\n \"claude-mythos-5-1\",\n \"claude-mythos-5\",\n \"claude-mythos-preview\",\n \"claude-fable-5\",\n \"claude-opus-5\",\n \"claude-sonnet-5\",\n];\n\n/** Families released before Claude Fable 5.1 in their entirety. */\nconst NO_PREFIX_CHECK_FAMILY_PREFIXES = [\n ...BUDGET_FAMILY_PREFIXES,\n \"claude-opus-4\",\n \"claude-sonnet-4\",\n];\n\n/**\n * Whether this model binds a replayed thinking block to the exact\n * conversation before it (preserved thinking). Unknown and future ids are\n * assumed to run the check — the safe direction: reasoning a kept turn\n * can't use is left out instead of risking a 400. Matches the id with or\n * without a platform prefix and with dated or tagged suffixes.\n *\n * @example\n * inferPreservedThinking(\"claude-opus-5-5\"); // → true\n * inferPreservedThinking(\"claude-opus-5\"); // → false\n * inferPreservedThinking(\"claude-opus-4-8\"); // → false\n */\nexport function inferPreservedThinking(modelName: string): boolean {\n const normalized = modelName.toLowerCase();\n const id = normalized.slice(normalized.lastIndexOf(\".\") + 1);\n\n if (PREFIX_CHECK_RELEASE_ROOTS.some((root) => matchesReleaseRoot(id, root))) {\n return true;\n }\n\n if (\n NO_PREFIX_CHECK_RELEASE_ROOTS.some((root) => matchesReleaseRoot(id, root)) ||\n NO_PREFIX_CHECK_FAMILY_PREFIXES.some((prefix) => id.startsWith(prefix))\n ) {\n return false;\n }\n\n return true;\n}\n"],"mappings":";;;;;AA4CA,MAAM,yBAAyB;CAE7B;CAEA;CACA;CAEA;AACF;;;;;;;;AASA,MAAM,uBAAuB;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;AAOA,MAAM,qBAAqB;;;;;;AAO3B,SAAgB,mBAAmB,MAAc,MAAuB;CACtE,IAAI,CAAC,KAAK,WAAW,IAAI,GACvB,OAAO;CAGT,MAAM,OAAO,KAAK,MAAM,KAAK,MAAM;CAEnC,IAAI,SAAS,IACX,OAAO;CAGT,QAAQ,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,GAAG,MAAM,CAAC,mBAAmB,KAAK,IAAI;AACxF;;;;;;;;;;;;AAaA,SAAgB,kBAAkB,WAA0C;CAC1E,MAAM,aAAa,UAAU,YAAY;CAEzC,IAAI,uBAAuB,MAAM,WAAW,WAAW,WAAW,MAAM,CAAC,GACvE,OAAO;CAGT,IAAI,qBAAqB,MAAM,SAAS,mBAAmB,YAAY,IAAI,CAAC,GAC1E,OAAO;CAGT,OAAO;AACT;;;;;;;;;;;;;;;;;AAkBA,SAAgB,mBAAmB,MAAsC;CACvE,OAAO,SAAS;AAClB;;;;;;;;;;;AAYA,MAAM,8BAA8B,CAAC,mBAAmB;;;;;;;;;;;AAYxD,SAAgB,oBAAoB,WAA4B;CAC9D,OAAO,kBAAkB,WAAW,2BAA2B;AACjE;;;;;;AAOA,SAAS,kBAAkB,WAAmB,OAAmC;CAC/E,MAAM,aAAa,UAAU,YAAY;CACzC,MAAM,KAAK,WAAW,MAAM,WAAW,YAAY,GAAG,IAAI,CAAC;CAE3D,OAAO,MAAM,MAAM,SAAS,mBAAmB,IAAI,IAAI,CAAC;AAC1D;;;;;;;;;AAsBA,MAAM,kCAAkC,CAAC,mBAAmB,kBAAkB;;;;;;;;;;;;AAa9E,MAAM,8BAA8B,CAAC,eAAe;;;;;;;;;;;;;;;;;;;;;AAsBpD,SAAgB,gBAAgB,WAA6C;CAC3E,IAAI,oBAAoB,SAAS,GAC/B,OAAO;CAGT,IAAI,kBAAkB,WAAW,+BAA+B,GAC9D,OAAO;CAGT,IAAI,kBAAkB,WAAW,2BAA2B,GAC1D,OAAO;CAGT,OAAO;AACT;AAWA,MAAM,6BAA6B;CACjC;CACA;CACA;CACA;AACF;;AAGA,MAAM,gCAAgC;CACpC;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAM,kCAAkC;CACtC,GAAG;CACH;CACA;AACF;;;;;;;;;;;;;AAcA,SAAgB,uBAAuB,WAA4B;CACjE,MAAM,aAAa,UAAU,YAAY;CACzC,MAAM,KAAK,WAAW,MAAM,WAAW,YAAY,GAAG,IAAI,CAAC;CAE3D,IAAI,2BAA2B,MAAM,SAAS,mBAAmB,IAAI,IAAI,CAAC,GACxE,OAAO;CAGT,IACE,8BAA8B,MAAM,SAAS,mBAAmB,IAAI,IAAI,CAAC,KACzE,gCAAgC,MAAM,WAAW,GAAG,WAAW,MAAM,CAAC,GAEtE,OAAO;CAGT,OAAO;AACT"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region ../../packages/ai-anthropic/src/utils/cache-usage.ts
|
|
2
|
+
/**
|
|
3
|
+
* Copy Anthropic's cache counters onto a neutral `Usage`.
|
|
4
|
+
*
|
|
5
|
+
* - `cache_read_input_tokens` → `cachedTokens`
|
|
6
|
+
* - `cache_creation_input_tokens` → `cacheWriteTokens`
|
|
7
|
+
* - `cache_creation.ephemeral_1h_input_tokens` → `cacheWrite1hTokens`
|
|
8
|
+
* (the 1-hour writes, billed at 2x input instead of 1.25x)
|
|
9
|
+
*
|
|
10
|
+
* When Anthropic omits the per-lifetime breakdown, `requestTtl` decides:
|
|
11
|
+
* a request whose every cache marker asked for `"1h"` wrote only 1-hour
|
|
12
|
+
* entries. Counters are set only when positive, so an absent channel
|
|
13
|
+
* stays undefined. Anthropic's `input_tokens` excludes all of these, so
|
|
14
|
+
* the caller marks the usage `inputIncludesCache: false`.
|
|
15
|
+
*/
|
|
16
|
+
function applyAnthropicCacheUsage(usage, raw, requestTtl) {
|
|
17
|
+
const cacheRead = raw.cache_read_input_tokens;
|
|
18
|
+
if (typeof cacheRead === "number" && cacheRead > 0) usage.cachedTokens = cacheRead;
|
|
19
|
+
const cacheWrite = raw.cache_creation_input_tokens;
|
|
20
|
+
if (typeof cacheWrite !== "number" || cacheWrite <= 0) return;
|
|
21
|
+
usage.cacheWriteTokens = cacheWrite;
|
|
22
|
+
const reported1h = raw.cache_creation?.ephemeral_1h_input_tokens;
|
|
23
|
+
const writes1h = typeof reported1h === "number" ? reported1h : requestTtl === "1h" ? cacheWrite : 0;
|
|
24
|
+
if (writes1h > 0) usage.cacheWrite1hTokens = Math.min(writes1h, cacheWrite);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Scan a request body (top level, system, tools, messages) for
|
|
28
|
+
* `cache_control` markers and report the lifetime they request. Used only
|
|
29
|
+
* as a fallback when the response lacks Anthropic's per-lifetime
|
|
30
|
+
* cache-write breakdown.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* requestCacheTtl({ cache_control: { type: "ephemeral", ttl: "1h" } }); // "1h"
|
|
34
|
+
*/
|
|
35
|
+
function requestCacheTtl(body) {
|
|
36
|
+
let found;
|
|
37
|
+
const visit = (value, depth) => {
|
|
38
|
+
if (depth > 8 || typeof value !== "object" || value === null) return;
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
for (const item of value) visit(item, depth + 1);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const record = value;
|
|
44
|
+
const marker = record.cache_control;
|
|
45
|
+
if (typeof marker === "object" && marker !== null) {
|
|
46
|
+
const ttl = marker.ttl === "1h" ? "1h" : "5m";
|
|
47
|
+
found = found === void 0 || found === ttl ? ttl : "mixed";
|
|
48
|
+
}
|
|
49
|
+
for (const key of [
|
|
50
|
+
"system",
|
|
51
|
+
"tools",
|
|
52
|
+
"messages",
|
|
53
|
+
"content"
|
|
54
|
+
]) if (key in record) visit(record[key], depth + 1);
|
|
55
|
+
};
|
|
56
|
+
visit(body, 0);
|
|
57
|
+
return found;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
export { applyAnthropicCacheUsage, requestCacheTtl };
|
|
62
|
+
//# sourceMappingURL=cache-usage.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-usage.mjs","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/cache-usage.ts"],"sourcesContent":["import type { Usage } from \"@warlock.js/ai\";\n\n/** The cache-related fields of an Anthropic usage block (final or streamed). */\nexport type AnthropicCacheUsage = {\n cache_read_input_tokens?: number | null;\n cache_creation_input_tokens?: number | null;\n cache_creation?: {\n ephemeral_1h_input_tokens?: number | null;\n ephemeral_5m_input_tokens?: number | null;\n } | null;\n};\n\n/**\n * Copy Anthropic's cache counters onto a neutral `Usage`.\n *\n * - `cache_read_input_tokens` → `cachedTokens`\n * - `cache_creation_input_tokens` → `cacheWriteTokens`\n * - `cache_creation.ephemeral_1h_input_tokens` → `cacheWrite1hTokens`\n * (the 1-hour writes, billed at 2x input instead of 1.25x)\n *\n * When Anthropic omits the per-lifetime breakdown, `requestTtl` decides:\n * a request whose every cache marker asked for `\"1h\"` wrote only 1-hour\n * entries. Counters are set only when positive, so an absent channel\n * stays undefined. Anthropic's `input_tokens` excludes all of these, so\n * the caller marks the usage `inputIncludesCache: false`.\n */\nexport function applyAnthropicCacheUsage(\n usage: Usage,\n raw: AnthropicCacheUsage,\n requestTtl: RequestCacheTtl,\n): void {\n const cacheRead = raw.cache_read_input_tokens;\n\n if (typeof cacheRead === \"number\" && cacheRead > 0) {\n usage.cachedTokens = cacheRead;\n }\n\n const cacheWrite = raw.cache_creation_input_tokens;\n\n if (typeof cacheWrite !== \"number\" || cacheWrite <= 0) {\n return;\n }\n\n usage.cacheWriteTokens = cacheWrite;\n\n const reported1h = raw.cache_creation?.ephemeral_1h_input_tokens;\n const writes1h =\n typeof reported1h === \"number\" ? reported1h : requestTtl === \"1h\" ? cacheWrite : 0;\n\n if (writes1h > 0) {\n usage.cacheWrite1hTokens = Math.min(writes1h, cacheWrite);\n }\n}\n\n/**\n * The cache lifetime a request body asked for: `\"1h\"` when every\n * `cache_control` marker in it says `ttl: \"1h\"`, `\"5m\"` when every marker\n * uses the default lifetime, `\"mixed\"` when both appear, and `undefined`\n * when the body carries no marker.\n */\nexport type RequestCacheTtl = \"1h\" | \"5m\" | \"mixed\" | undefined;\n\n/**\n * Scan a request body (top level, system, tools, messages) for\n * `cache_control` markers and report the lifetime they request. Used only\n * as a fallback when the response lacks Anthropic's per-lifetime\n * cache-write breakdown.\n *\n * @example\n * requestCacheTtl({ cache_control: { type: \"ephemeral\", ttl: \"1h\" } }); // \"1h\"\n */\nexport function requestCacheTtl(body: unknown): RequestCacheTtl {\n let found: RequestCacheTtl;\n\n const visit = (value: unknown, depth: number): void => {\n if (depth > 8 || typeof value !== \"object\" || value === null) {\n return;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n visit(item, depth + 1);\n }\n\n return;\n }\n\n const record = value as Record<string, unknown>;\n const marker = record.cache_control;\n\n if (typeof marker === \"object\" && marker !== null) {\n const ttl = (marker as { ttl?: unknown }).ttl === \"1h\" ? \"1h\" : \"5m\";\n found = found === undefined || found === ttl ? ttl : \"mixed\";\n }\n\n for (const key of [\"system\", \"tools\", \"messages\", \"content\"] as const) {\n if (key in record) {\n visit(record[key], depth + 1);\n }\n }\n };\n\n visit(body, 0);\n\n return found;\n}\n"],"mappings":";;;;;;;;;;;;;;;AA0BA,SAAgB,yBACd,OACA,KACA,YACM;CACN,MAAM,YAAY,IAAI;CAEtB,IAAI,OAAO,cAAc,YAAY,YAAY,GAC/C,MAAM,eAAe;CAGvB,MAAM,aAAa,IAAI;CAEvB,IAAI,OAAO,eAAe,YAAY,cAAc,GAClD;CAGF,MAAM,mBAAmB;CAEzB,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,WACJ,OAAO,eAAe,WAAW,aAAa,eAAe,OAAO,aAAa;CAEnF,IAAI,WAAW,GACb,MAAM,qBAAqB,KAAK,IAAI,UAAU,UAAU;AAE5D;;;;;;;;;;AAmBA,SAAgB,gBAAgB,MAAgC;CAC9D,IAAI;CAEJ,MAAM,SAAS,OAAgB,UAAwB;EACrD,IAAI,QAAQ,KAAK,OAAO,UAAU,YAAY,UAAU,MACtD;EAGF,IAAI,MAAM,QAAQ,KAAK,GAAG;GACxB,KAAK,MAAM,QAAQ,OACjB,MAAM,MAAM,QAAQ,CAAC;GAGvB;EACF;EAEA,MAAM,SAAS;EACf,MAAM,SAAS,OAAO;EAEtB,IAAI,OAAO,WAAW,YAAY,WAAW,MAAM;GACjD,MAAM,MAAO,OAA6B,QAAQ,OAAO,OAAO;GAChE,QAAQ,UAAU,UAAa,UAAU,MAAM,MAAM;EACvD;EAEA,KAAK,MAAM,OAAO;GAAC;GAAU;GAAS;GAAY;EAAS,GACzD,IAAI,OAAO,QACT,MAAM,OAAO,MAAM,QAAQ,CAAC;CAGlC;CAEA,MAAM,MAAM,CAAC;CAEb,OAAO;AACT"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ANTHROPIC_BLOCK_FORMAT, fromProviderBlocks, toProviderBlocks } from "./provider-blocks.mjs";
|
|
2
|
+
import { AnthropicMessages, ToAnthropicMessagesOptions, toAnthropicMessages } from "./to-anthropic-messages.mjs";
|
|
3
|
+
import { toAnthropicTools } from "./to-anthropic-tools.mjs";
|
package/esm/utils/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { mapStopReason } from "./map-stop-reason.mjs";
|
|
2
|
+
import { ANTHROPIC_BLOCK_FORMAT, fromProviderBlocks, toProviderBlocks } from "./provider-blocks.mjs";
|
|
2
3
|
import { toAnthropicMessages } from "./to-anthropic-messages.mjs";
|
|
3
4
|
import { toAnthropicTools } from "./to-anthropic-tools.mjs";
|
|
4
5
|
import { wrapAnthropicError } from "./wrap-anthropic-error.mjs";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -1,27 +1,31 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region ../../packages/ai-anthropic/src/utils/map-stop-reason.ts
|
|
2
2
|
const stopReasonMap = {
|
|
3
3
|
end_turn: "stop",
|
|
4
4
|
stop_sequence: "stop",
|
|
5
|
+
compaction: "stop",
|
|
5
6
|
max_tokens: "length",
|
|
6
|
-
model_context_window_exceeded: "
|
|
7
|
+
model_context_window_exceeded: "context_exceeded",
|
|
8
|
+
pause_turn: "pause_turn",
|
|
9
|
+
refusal: "refusal",
|
|
7
10
|
tool_use: "tool_calls"
|
|
8
11
|
};
|
|
9
12
|
/**
|
|
10
13
|
* Map Anthropic's `stop_reason` to the normalized `FinishReason` union.
|
|
11
14
|
*
|
|
12
|
-
* `end_turn` / `stop_sequence` are
|
|
13
|
-
* `
|
|
14
|
-
* `
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* `end_turn` / `stop_sequence` (and beta `compaction`) are natural stops,
|
|
16
|
+
* `max_tokens` maps to `length`, `tool_use` to `tool_calls`.
|
|
17
|
+
* `model_context_window_exceeded`, `pause_turn` and `refusal` keep their
|
|
18
|
+
* own reasons (`context_exceeded`, `pause_turn`, `refusal`) because each
|
|
19
|
+
* needs a different reaction: compact the history, send the turn back to
|
|
20
|
+
* resume, or fall back to another model. `null` and any value a future
|
|
21
|
+
* API version adds fall through to `"error"`.
|
|
19
22
|
*
|
|
20
23
|
* @example
|
|
21
|
-
* mapStopReason("end_turn");
|
|
22
|
-
* mapStopReason("tool_use");
|
|
23
|
-
* mapStopReason("refusal");
|
|
24
|
-
* mapStopReason(
|
|
24
|
+
* mapStopReason("end_turn"); // "stop"
|
|
25
|
+
* mapStopReason("tool_use"); // "tool_calls"
|
|
26
|
+
* mapStopReason("refusal"); // "refusal"
|
|
27
|
+
* mapStopReason("pause_turn"); // "pause_turn"
|
|
28
|
+
* mapStopReason(null); // "error"
|
|
25
29
|
*/
|
|
26
30
|
function mapStopReason(raw) {
|
|
27
31
|
return stopReasonMap[raw ?? ""] ?? "error";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map-stop-reason.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"map-stop-reason.mjs","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/map-stop-reason.ts"],"sourcesContent":["import type { FinishReason } from \"@warlock.js/ai\";\n\nconst stopReasonMap: Record<string, FinishReason> = {\n end_turn: \"stop\",\n stop_sequence: \"stop\",\n // Beta compaction (`compact-2026-09-04`): the turn ends with a signed\n // `compaction` block and nothing is sampled after it. A clean stop —\n // the block itself is on `ModelResponse.blocks`.\n compaction: \"stop\",\n max_tokens: \"length\",\n model_context_window_exceeded: \"context_exceeded\",\n pause_turn: \"pause_turn\",\n refusal: \"refusal\",\n tool_use: \"tool_calls\",\n};\n\n/**\n * Map Anthropic's `stop_reason` to the normalized `FinishReason` union.\n *\n * `end_turn` / `stop_sequence` (and beta `compaction`) are natural stops,\n * `max_tokens` maps to `length`, `tool_use` to `tool_calls`.\n * `model_context_window_exceeded`, `pause_turn` and `refusal` keep their\n * own reasons (`context_exceeded`, `pause_turn`, `refusal`) because each\n * needs a different reaction: compact the history, send the turn back to\n * resume, or fall back to another model. `null` and any value a future\n * API version adds fall through to `\"error\"`.\n *\n * @example\n * mapStopReason(\"end_turn\"); // \"stop\"\n * mapStopReason(\"tool_use\"); // \"tool_calls\"\n * mapStopReason(\"refusal\"); // \"refusal\"\n * mapStopReason(\"pause_turn\"); // \"pause_turn\"\n * mapStopReason(null); // \"error\"\n */\nexport function mapStopReason(raw: string | null | undefined): FinishReason {\n return stopReasonMap[raw ?? \"\"] ?? \"error\";\n}\n"],"mappings":";AAEA,MAAM,gBAA8C;CAClD,UAAU;CACV,eAAe;CAIf,YAAY;CACZ,YAAY;CACZ,+BAA+B;CAC/B,YAAY;CACZ,SAAS;CACT,UAAU;AACZ;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,cAAc,KAA8C;CAC1E,OAAO,cAAc,OAAO,OAAO;AACrC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { ModelToolCallRequest, ProviderContentBlock } from "@warlock.js/ai";
|
|
3
|
+
|
|
4
|
+
//#region ../../packages/ai-anthropic/src/utils/provider-blocks.d.ts
|
|
5
|
+
/** The `ProviderContentBlock.provider` value this adapter writes and replays. */
|
|
6
|
+
declare const ANTHROPIC_BLOCK_FORMAT = "anthropic";
|
|
7
|
+
/**
|
|
8
|
+
* Wrap an assistant turn's content blocks as `ProviderContentBlock`s, or
|
|
9
|
+
* return `undefined` when the neutral fields already carry the whole turn
|
|
10
|
+
* (only plain text and `tool_use` blocks). Any other block — thinking,
|
|
11
|
+
* redacted thinking, compaction, server-tool use/results, cited text —
|
|
12
|
+
* makes the full ordered list worth keeping so the next request can
|
|
13
|
+
* replay the turn verbatim.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* toProviderBlocks([{ type: "text", text: "hi", citations: null }]); // undefined
|
|
17
|
+
* toProviderBlocks([{ type: "thinking", thinking: "", signature: "s" }, { type: "text", text: "hi" }]);
|
|
18
|
+
* // [{ provider: "anthropic", type: "thinking", data: {...} }, { provider: "anthropic", type: "text", data: {...} }]
|
|
19
|
+
*/
|
|
20
|
+
declare function toProviderBlocks(content: ReadonlyArray<{
|
|
21
|
+
type: string;
|
|
22
|
+
}>): ProviderContentBlock[] | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Rebuild an assistant turn's Anthropic content from stored blocks.
|
|
25
|
+
* Returns `undefined` — so the caller falls back to `content` +
|
|
26
|
+
* `toolCalls` — when there are no blocks, when any block belongs to
|
|
27
|
+
* another provider's format, when a block is not a well-formed
|
|
28
|
+
* assistant-turn block (its `data.type` must equal its `type`, and the
|
|
29
|
+
* type must be one Anthropic returns on an assistant turn), or when the
|
|
30
|
+
* blocks' `tool_use` ids differ from the message's `toolCalls` (the turn
|
|
31
|
+
* was edited after the response, e.g. by a middleware or a recovered
|
|
32
|
+
* stream call, so a verbatim replay would no longer match the tool
|
|
33
|
+
* results that follow). The checks matter when a transcript comes from
|
|
34
|
+
* outside the process (a client, a store): the block data is sent as is.
|
|
35
|
+
*/
|
|
36
|
+
declare function fromProviderBlocks(blocks: ProviderContentBlock[] | undefined, toolCalls: ModelToolCallRequest[] | undefined): Anthropic.ContentBlockParam[] | undefined;
|
|
37
|
+
//#endregion
|
|
38
|
+
export { ANTHROPIC_BLOCK_FORMAT, fromProviderBlocks, toProviderBlocks };
|
|
39
|
+
//# sourceMappingURL=provider-blocks.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-blocks.d.mts","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/provider-blocks.ts"],"mappings":";;;;;cAIa,sBAAA;AAAb;;;;AAAmC;AA0CnC;;;;;;;;AA1CA,iBA0CgB,gBAAA,CACd,OAAA,EAAS,aAAA;EAAgB,IAAA;AAAA,KACxB,oBAAoB;;;;;;;;;;;;;;iBAyBP,kBAAA,CACd,MAAA,EAAQ,oBAAA,gBACR,SAAA,EAAW,oBAAA,iBACV,SAAA,CAAU,iBAAA"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
//#region ../../packages/ai-anthropic/src/utils/provider-blocks.ts
|
|
2
|
+
/** The `ProviderContentBlock.provider` value this adapter writes and replays. */
|
|
3
|
+
const ANTHROPIC_BLOCK_FORMAT = "anthropic";
|
|
4
|
+
/** Block types the neutral `content` + `toolCalls` fields already carry losslessly. */
|
|
5
|
+
const NEUTRAL_BLOCK_TYPES = new Set(["text", "tool_use"]);
|
|
6
|
+
/** Block types that carry a model-requested call whose id must match `toolCalls`. */
|
|
7
|
+
const TOOL_USE_BLOCK_TYPES = new Set(["tool_use"]);
|
|
8
|
+
/**
|
|
9
|
+
* Assistant-turn block types that may be sent back verbatim. Server-tool
|
|
10
|
+
* results all end in `_tool_result` (`web_search_tool_result`,
|
|
11
|
+
* `code_execution_tool_result`, …) and are matched by suffix; anything
|
|
12
|
+
* else (an unknown or user-side type such as `image`, `document` or a
|
|
13
|
+
* client `tool_result`) is not replayed.
|
|
14
|
+
*/
|
|
15
|
+
const REPLAYABLE_BLOCK_TYPES = new Set([
|
|
16
|
+
"text",
|
|
17
|
+
"thinking",
|
|
18
|
+
"redacted_thinking",
|
|
19
|
+
"tool_use",
|
|
20
|
+
"server_tool_use",
|
|
21
|
+
"mcp_tool_use",
|
|
22
|
+
"mcp_tool_result",
|
|
23
|
+
"container_upload",
|
|
24
|
+
"compaction"
|
|
25
|
+
]);
|
|
26
|
+
const SERVER_TOOL_RESULT_TYPE = /^[a-z][a-z0-9_]*_tool_result$/;
|
|
27
|
+
/**
|
|
28
|
+
* Wrap an assistant turn's content blocks as `ProviderContentBlock`s, or
|
|
29
|
+
* return `undefined` when the neutral fields already carry the whole turn
|
|
30
|
+
* (only plain text and `tool_use` blocks). Any other block — thinking,
|
|
31
|
+
* redacted thinking, compaction, server-tool use/results, cited text —
|
|
32
|
+
* makes the full ordered list worth keeping so the next request can
|
|
33
|
+
* replay the turn verbatim.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* toProviderBlocks([{ type: "text", text: "hi", citations: null }]); // undefined
|
|
37
|
+
* toProviderBlocks([{ type: "thinking", thinking: "", signature: "s" }, { type: "text", text: "hi" }]);
|
|
38
|
+
* // [{ provider: "anthropic", type: "thinking", data: {...} }, { provider: "anthropic", type: "text", data: {...} }]
|
|
39
|
+
*/
|
|
40
|
+
function toProviderBlocks(content) {
|
|
41
|
+
if (!content.some(needsVerbatimReplay)) return;
|
|
42
|
+
return content.map((block) => ({
|
|
43
|
+
provider: ANTHROPIC_BLOCK_FORMAT,
|
|
44
|
+
type: block.type,
|
|
45
|
+
data: { ...block }
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Rebuild an assistant turn's Anthropic content from stored blocks.
|
|
50
|
+
* Returns `undefined` — so the caller falls back to `content` +
|
|
51
|
+
* `toolCalls` — when there are no blocks, when any block belongs to
|
|
52
|
+
* another provider's format, when a block is not a well-formed
|
|
53
|
+
* assistant-turn block (its `data.type` must equal its `type`, and the
|
|
54
|
+
* type must be one Anthropic returns on an assistant turn), or when the
|
|
55
|
+
* blocks' `tool_use` ids differ from the message's `toolCalls` (the turn
|
|
56
|
+
* was edited after the response, e.g. by a middleware or a recovered
|
|
57
|
+
* stream call, so a verbatim replay would no longer match the tool
|
|
58
|
+
* results that follow). The checks matter when a transcript comes from
|
|
59
|
+
* outside the process (a client, a store): the block data is sent as is.
|
|
60
|
+
*/
|
|
61
|
+
function fromProviderBlocks(blocks, toolCalls) {
|
|
62
|
+
if (!blocks || blocks.length === 0) return;
|
|
63
|
+
if (!blocks.every((block) => block.provider === "anthropic" && isReplayable(block))) return;
|
|
64
|
+
const blockToolIds = blocks.filter((block) => TOOL_USE_BLOCK_TYPES.has(block.type)).map((block) => block.data.id);
|
|
65
|
+
const requestedIds = (toolCalls ?? []).map((toolCall) => toolCall.id);
|
|
66
|
+
if (blockToolIds.length !== requestedIds.length || blockToolIds.some((id, index) => id !== requestedIds[index])) return;
|
|
67
|
+
return blocks.map((block) => ({ ...block.data }));
|
|
68
|
+
}
|
|
69
|
+
function needsVerbatimReplay(block) {
|
|
70
|
+
if (!NEUTRAL_BLOCK_TYPES.has(block.type)) return true;
|
|
71
|
+
const citations = block.citations;
|
|
72
|
+
return Array.isArray(citations) && citations.length > 0;
|
|
73
|
+
}
|
|
74
|
+
function isReplayable(block) {
|
|
75
|
+
const data = block.data;
|
|
76
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) return false;
|
|
77
|
+
if (data.type !== block.type) return false;
|
|
78
|
+
return REPLAYABLE_BLOCK_TYPES.has(block.type) || SERVER_TOOL_RESULT_TYPE.test(block.type);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
export { ANTHROPIC_BLOCK_FORMAT, fromProviderBlocks, toProviderBlocks };
|
|
83
|
+
//# sourceMappingURL=provider-blocks.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-blocks.mjs","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/provider-blocks.ts"],"sourcesContent":["import type { ModelToolCallRequest, ProviderContentBlock } from \"@warlock.js/ai\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\n\n/** The `ProviderContentBlock.provider` value this adapter writes and replays. */\nexport const ANTHROPIC_BLOCK_FORMAT = \"anthropic\";\n\n/** Block types the neutral `content` + `toolCalls` fields already carry losslessly. */\nconst NEUTRAL_BLOCK_TYPES = new Set([\"text\", \"tool_use\"]);\n\n/** Block types that carry a model-requested call whose id must match `toolCalls`. */\nconst TOOL_USE_BLOCK_TYPES = new Set([\"tool_use\"]);\n\n/**\n * Assistant-turn block types that may be sent back verbatim. Server-tool\n * results all end in `_tool_result` (`web_search_tool_result`,\n * `code_execution_tool_result`, …) and are matched by suffix; anything\n * else (an unknown or user-side type such as `image`, `document` or a\n * client `tool_result`) is not replayed.\n */\nconst REPLAYABLE_BLOCK_TYPES: ReadonlySet<string> = new Set([\n \"text\",\n \"thinking\",\n \"redacted_thinking\",\n \"tool_use\",\n \"server_tool_use\",\n \"mcp_tool_use\",\n \"mcp_tool_result\",\n \"container_upload\",\n \"compaction\",\n]);\n\nconst SERVER_TOOL_RESULT_TYPE = /^[a-z][a-z0-9_]*_tool_result$/;\n\n/**\n * Wrap an assistant turn's content blocks as `ProviderContentBlock`s, or\n * return `undefined` when the neutral fields already carry the whole turn\n * (only plain text and `tool_use` blocks). Any other block — thinking,\n * redacted thinking, compaction, server-tool use/results, cited text —\n * makes the full ordered list worth keeping so the next request can\n * replay the turn verbatim.\n *\n * @example\n * toProviderBlocks([{ type: \"text\", text: \"hi\", citations: null }]); // undefined\n * toProviderBlocks([{ type: \"thinking\", thinking: \"\", signature: \"s\" }, { type: \"text\", text: \"hi\" }]);\n * // [{ provider: \"anthropic\", type: \"thinking\", data: {...} }, { provider: \"anthropic\", type: \"text\", data: {...} }]\n */\nexport function toProviderBlocks(\n content: ReadonlyArray<{ type: string }>,\n): ProviderContentBlock[] | undefined {\n if (!content.some(needsVerbatimReplay)) {\n return undefined;\n }\n\n return content.map((block) => ({\n provider: ANTHROPIC_BLOCK_FORMAT,\n type: block.type,\n data: { ...(block as Record<string, unknown>) },\n }));\n}\n\n/**\n * Rebuild an assistant turn's Anthropic content from stored blocks.\n * Returns `undefined` — so the caller falls back to `content` +\n * `toolCalls` — when there are no blocks, when any block belongs to\n * another provider's format, when a block is not a well-formed\n * assistant-turn block (its `data.type` must equal its `type`, and the\n * type must be one Anthropic returns on an assistant turn), or when the\n * blocks' `tool_use` ids differ from the message's `toolCalls` (the turn\n * was edited after the response, e.g. by a middleware or a recovered\n * stream call, so a verbatim replay would no longer match the tool\n * results that follow). The checks matter when a transcript comes from\n * outside the process (a client, a store): the block data is sent as is.\n */\nexport function fromProviderBlocks(\n blocks: ProviderContentBlock[] | undefined,\n toolCalls: ModelToolCallRequest[] | undefined,\n): Anthropic.ContentBlockParam[] | undefined {\n if (!blocks || blocks.length === 0) {\n return undefined;\n }\n\n if (!blocks.every((block) => block.provider === ANTHROPIC_BLOCK_FORMAT && isReplayable(block))) {\n return undefined;\n }\n\n const blockToolIds = blocks\n .filter((block) => TOOL_USE_BLOCK_TYPES.has(block.type))\n .map((block) => block.data.id);\n const requestedIds = (toolCalls ?? []).map((toolCall) => toolCall.id);\n\n if (\n blockToolIds.length !== requestedIds.length ||\n blockToolIds.some((id, index) => id !== requestedIds[index])\n ) {\n return undefined;\n }\n\n return blocks.map((block) => ({ ...block.data }) as unknown as Anthropic.ContentBlockParam);\n}\n\nfunction needsVerbatimReplay(block: { type: string }): boolean {\n if (!NEUTRAL_BLOCK_TYPES.has(block.type)) {\n return true;\n }\n\n const citations = (block as { citations?: unknown }).citations;\n\n return Array.isArray(citations) && citations.length > 0;\n}\n\nfunction isReplayable(block: ProviderContentBlock): boolean {\n const data = block.data as unknown;\n\n if (data === null || typeof data !== \"object\" || Array.isArray(data)) {\n return false;\n }\n\n if ((data as { type?: unknown }).type !== block.type) {\n return false;\n }\n\n return REPLAYABLE_BLOCK_TYPES.has(block.type) || SERVER_TOOL_RESULT_TYPE.test(block.type);\n}\n"],"mappings":";;AAIA,MAAa,yBAAyB;;AAGtC,MAAM,sBAAsB,IAAI,IAAI,CAAC,QAAQ,UAAU,CAAC;;AAGxD,MAAM,uBAAuB,IAAI,IAAI,CAAC,UAAU,CAAC;;;;;;;;AASjD,MAAM,yBAA8C,IAAI,IAAI;CAC1D;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,MAAM,0BAA0B;;;;;;;;;;;;;;AAehC,SAAgB,iBACd,SACoC;CACpC,IAAI,CAAC,QAAQ,KAAK,mBAAmB,GACnC;CAGF,OAAO,QAAQ,KAAK,WAAW;EAC7B,UAAU;EACV,MAAM,MAAM;EACZ,MAAM,EAAE,GAAI,MAAkC;CAChD,EAAE;AACJ;;;;;;;;;;;;;;AAeA,SAAgB,mBACd,QACA,WAC2C;CAC3C,IAAI,CAAC,UAAU,OAAO,WAAW,GAC/B;CAGF,IAAI,CAAC,OAAO,OAAO,UAAU,MAAM,4BAAuC,aAAa,KAAK,CAAC,GAC3F;CAGF,MAAM,eAAe,OAClB,QAAQ,UAAU,qBAAqB,IAAI,MAAM,IAAI,CAAC,CAAC,CACvD,KAAK,UAAU,MAAM,KAAK,EAAE;CAC/B,MAAM,gBAAgB,aAAa,CAAC,EAAC,CAAE,KAAK,aAAa,SAAS,EAAE;CAEpE,IACE,aAAa,WAAW,aAAa,UACrC,aAAa,MAAM,IAAI,UAAU,OAAO,aAAa,MAAM,GAE3D;CAGF,OAAO,OAAO,KAAK,WAAW,EAAE,GAAG,MAAM,KAAK,EAA4C;AAC5F;AAEA,SAAS,oBAAoB,OAAkC;CAC7D,IAAI,CAAC,oBAAoB,IAAI,MAAM,IAAI,GACrC,OAAO;CAGT,MAAM,YAAa,MAAkC;CAErD,OAAO,MAAM,QAAQ,SAAS,KAAK,UAAU,SAAS;AACxD;AAEA,SAAS,aAAa,OAAsC;CAC1D,MAAM,OAAO,MAAM;CAEnB,IAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GACjE,OAAO;CAGT,IAAK,KAA4B,SAAS,MAAM,MAC9C,OAAO;CAGT,OAAO,uBAAuB,IAAI,MAAM,IAAI,KAAK,wBAAwB,KAAK,MAAM,IAAI;AAC1F"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { Message } from "@warlock.js/ai";
|
|
3
|
+
|
|
4
|
+
//#region ../../packages/ai-anthropic/src/utils/to-anthropic-messages.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Result of splitting a vendor-neutral `Message[]` for the Anthropic
|
|
7
|
+
* Messages API: the system prompt is hoisted to a top-level `system`
|
|
8
|
+
* string (Anthropic has no `"system"` role inside `messages`), and the
|
|
9
|
+
* remaining turns are mapped to `MessageParam[]`.
|
|
10
|
+
*/
|
|
11
|
+
type AnthropicMessages = {
|
|
12
|
+
system: string | undefined;
|
|
13
|
+
messages: Anthropic.MessageParam[];
|
|
14
|
+
};
|
|
15
|
+
/** Options for {@link toAnthropicMessages}. */
|
|
16
|
+
type ToAnthropicMessagesOptions = {
|
|
17
|
+
/**
|
|
18
|
+
* Leave out the `thinking` / `redacted_thinking` blocks of assistant turns
|
|
19
|
+
* marked `compaction: "retained"` — turns kept verbatim after a
|
|
20
|
+
* client-written summary. On models that run the preserved-thinking
|
|
21
|
+
* conversation check those blocks were produced against the replaced
|
|
22
|
+
* history, so replaying them is a 400 (or a server-side drop). Text and
|
|
23
|
+
* tool-use blocks stay. Removing them is the no-beta path Anthropic
|
|
24
|
+
* documents for compaction that keeps recent turns.
|
|
25
|
+
*/
|
|
26
|
+
dropRetainedThinking?: boolean;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Convert vendor-neutral `Message[]` into Anthropic's request shape.
|
|
30
|
+
*
|
|
31
|
+
* Anthropic differs from the OpenAI Chat protocol in three ways this
|
|
32
|
+
* function absorbs:
|
|
33
|
+
*
|
|
34
|
+
* 1. **No `system` role.** System messages are concatenated (newline-
|
|
35
|
+
* separated) and returned separately as the top-level `system`
|
|
36
|
+
* parameter.
|
|
37
|
+
* 2. **Tool results are `user` turns.** A neutral `tool` message becomes
|
|
38
|
+
* a `tool_result` block keyed by `tool_use_id`. Consecutive tool
|
|
39
|
+
* messages (the results of one parallel tool-use turn) are grouped
|
|
40
|
+
* into a single `user` message, as Anthropic expects — splitting them
|
|
41
|
+
* across messages teaches the model to stop calling tools in parallel.
|
|
42
|
+
* 3. **Tool calls are `tool_use` content blocks.** An assistant message
|
|
43
|
+
* carrying `toolCalls` becomes an `assistant` message whose content
|
|
44
|
+
* is an optional leading `text` block followed by one `tool_use`
|
|
45
|
+
* block per call.
|
|
46
|
+
*
|
|
47
|
+
* An assistant message that carries Anthropic `blocks` (thinking,
|
|
48
|
+
* redacted thinking, compaction, server-tool blocks — see
|
|
49
|
+
* `ProviderContentBlock`) is replayed verbatim from those blocks, which
|
|
50
|
+
* keeps signed thinking and compaction state valid. Blocks from another
|
|
51
|
+
* provider, or blocks whose `tool_use` ids no longer match `toolCalls`,
|
|
52
|
+
* are ignored and the turn is rebuilt from `content` + `toolCalls`.
|
|
53
|
+
*
|
|
54
|
+
* With `dropRetainedThinking`, a turn marked `compaction: "retained"` is
|
|
55
|
+
* replayed without its thinking blocks (see {@link ToAnthropicMessagesOptions}).
|
|
56
|
+
*
|
|
57
|
+
* Other consecutive same-role turns are left as-is — the Messages API
|
|
58
|
+
* merges them server-side.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* const { system, messages } = toAnthropicMessages([
|
|
62
|
+
* { role: "system", content: "Be concise." },
|
|
63
|
+
* { role: "user", content: "Hi" },
|
|
64
|
+
* ]);
|
|
65
|
+
* // system === "Be concise."
|
|
66
|
+
* // messages === [{ role: "user", content: "Hi" }]
|
|
67
|
+
*/
|
|
68
|
+
declare function toAnthropicMessages(messages: Message[], options?: ToAnthropicMessagesOptions): AnthropicMessages;
|
|
69
|
+
//#endregion
|
|
70
|
+
export { AnthropicMessages, ToAnthropicMessagesOptions, toAnthropicMessages };
|
|
71
|
+
//# sourceMappingURL=to-anthropic-messages.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"to-anthropic-messages.d.mts","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/to-anthropic-messages.ts"],"mappings":";;;;;;AAUA;;;;KAAY,iBAAA;EACV,MAAA;EACA,QAAA,EAAU,SAAA,CAAU,YAAY;AAAA;;KAItB,0BAAA;EAAA;;;;AAUU;AA+CtB;;;;EA/CE,oBAAoB;AAAA;;;;;;;;;AAkDF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAHJ,mBAAA,CACd,QAAA,EAAU,OAAA,IACV,OAAA,GAAS,0BAAA,GACR,iBAAA"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { fromProviderBlocks } from "./provider-blocks.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../../packages/ai-anthropic/src/utils/to-anthropic-messages.ts
|
|
2
4
|
/**
|
|
3
5
|
* Convert vendor-neutral `Message[]` into Anthropic's request shape.
|
|
4
6
|
*
|
|
@@ -9,15 +11,27 @@
|
|
|
9
11
|
* separated) and returned separately as the top-level `system`
|
|
10
12
|
* parameter.
|
|
11
13
|
* 2. **Tool results are `user` turns.** A neutral `tool` message becomes
|
|
12
|
-
* a `
|
|
13
|
-
*
|
|
14
|
+
* a `tool_result` block keyed by `tool_use_id`. Consecutive tool
|
|
15
|
+
* messages (the results of one parallel tool-use turn) are grouped
|
|
16
|
+
* into a single `user` message, as Anthropic expects — splitting them
|
|
17
|
+
* across messages teaches the model to stop calling tools in parallel.
|
|
14
18
|
* 3. **Tool calls are `tool_use` content blocks.** An assistant message
|
|
15
19
|
* carrying `toolCalls` becomes an `assistant` message whose content
|
|
16
20
|
* is an optional leading `text` block followed by one `tool_use`
|
|
17
21
|
* block per call.
|
|
18
22
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
23
|
+
* An assistant message that carries Anthropic `blocks` (thinking,
|
|
24
|
+
* redacted thinking, compaction, server-tool blocks — see
|
|
25
|
+
* `ProviderContentBlock`) is replayed verbatim from those blocks, which
|
|
26
|
+
* keeps signed thinking and compaction state valid. Blocks from another
|
|
27
|
+
* provider, or blocks whose `tool_use` ids no longer match `toolCalls`,
|
|
28
|
+
* are ignored and the turn is rebuilt from `content` + `toolCalls`.
|
|
29
|
+
*
|
|
30
|
+
* With `dropRetainedThinking`, a turn marked `compaction: "retained"` is
|
|
31
|
+
* replayed without its thinking blocks (see {@link ToAnthropicMessagesOptions}).
|
|
32
|
+
*
|
|
33
|
+
* Other consecutive same-role turns are left as-is — the Messages API
|
|
34
|
+
* merges them server-side.
|
|
21
35
|
*
|
|
22
36
|
* @example
|
|
23
37
|
* const { system, messages } = toAnthropicMessages([
|
|
@@ -27,7 +41,7 @@
|
|
|
27
41
|
* // system === "Be concise."
|
|
28
42
|
* // messages === [{ role: "user", content: "Hi" }]
|
|
29
43
|
*/
|
|
30
|
-
function toAnthropicMessages(messages) {
|
|
44
|
+
function toAnthropicMessages(messages, options = {}) {
|
|
31
45
|
const systemParts = [];
|
|
32
46
|
const mapped = [];
|
|
33
47
|
for (const message of messages) {
|
|
@@ -36,18 +50,33 @@ function toAnthropicMessages(messages) {
|
|
|
36
50
|
continue;
|
|
37
51
|
}
|
|
38
52
|
if (message.role === "tool") {
|
|
39
|
-
|
|
53
|
+
const toolResult = {
|
|
54
|
+
type: "tool_result",
|
|
55
|
+
tool_use_id: message.toolCallId ?? "",
|
|
56
|
+
content: stringifyContent(message.content)
|
|
57
|
+
};
|
|
58
|
+
const previous = mapped[mapped.length - 1];
|
|
59
|
+
if (previous && previous.role === "user" && isToolResultTurn(previous)) previous.content.push(toolResult);
|
|
60
|
+
else mapped.push({
|
|
40
61
|
role: "user",
|
|
41
|
-
content: [
|
|
42
|
-
type: "tool_result",
|
|
43
|
-
tool_use_id: message.toolCallId ?? "",
|
|
44
|
-
content: stringifyContent(message.content)
|
|
45
|
-
}]
|
|
62
|
+
content: [toolResult]
|
|
46
63
|
});
|
|
47
64
|
continue;
|
|
48
65
|
}
|
|
66
|
+
const dropThinking = options.dropRetainedThinking === true && message.compaction === "retained";
|
|
67
|
+
if (message.role === "assistant") {
|
|
68
|
+
const replayed = fromProviderBlocks(message.blocks, message.toolCalls);
|
|
69
|
+
const content = dropThinking ? replayed?.filter((block) => !isThinkingBlock(block)) : replayed;
|
|
70
|
+
if (content && content.length > 0) {
|
|
71
|
+
mapped.push({
|
|
72
|
+
role: "assistant",
|
|
73
|
+
content
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
49
78
|
if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
|
|
50
|
-
const blocks = extractThinkingBlocks(message.toolCalls);
|
|
79
|
+
const blocks = dropThinking ? [] : extractThinkingBlocks(message.toolCalls);
|
|
51
80
|
const text = stringifyContent(message.content);
|
|
52
81
|
if (text) blocks.push({
|
|
53
82
|
type: "text",
|
|
@@ -82,6 +111,9 @@ function toAnthropicMessages(messages) {
|
|
|
82
111
|
messages: mapped
|
|
83
112
|
};
|
|
84
113
|
}
|
|
114
|
+
function isThinkingBlock(block) {
|
|
115
|
+
return block.type === "thinking" || block.type === "redacted_thinking";
|
|
116
|
+
}
|
|
85
117
|
/**
|
|
86
118
|
* Recover signed extended-thinking blocks stored by this adapter on a tool
|
|
87
119
|
* request. Anthropic requires these blocks to be replayed before the
|
|
@@ -108,6 +140,14 @@ function toAnthropicThinkingBlock(block) {
|
|
|
108
140
|
}];
|
|
109
141
|
return [];
|
|
110
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* A `user` turn this function built from tool results — its content is a
|
|
145
|
+
* non-empty array made only of `tool_result` blocks. Later tool results
|
|
146
|
+
* of the same tool-use turn are appended to it.
|
|
147
|
+
*/
|
|
148
|
+
function isToolResultTurn(message) {
|
|
149
|
+
return Array.isArray(message.content) && message.content.length > 0 && message.content.every((block) => block.type === "tool_result");
|
|
150
|
+
}
|
|
111
151
|
function isRecord(value) {
|
|
112
152
|
return typeof value === "object" && value !== null;
|
|
113
153
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"to-anthropic-messages.mjs","names":[],"sources":["../../../../../../../ai-anthropic/src/utils/to-anthropic-messages.ts"],"sourcesContent":["import type { ContentPart, Message, ModelToolCallRequest } from \"@warlock.js/ai\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\n\n/**\n * Result of splitting a vendor-neutral `Message[]` for the Anthropic\n * Messages API: the system prompt is hoisted to a top-level `system`\n * string (Anthropic has no `\"system\"` role inside `messages`), and the\n * remaining turns are mapped to `MessageParam[]`.\n */\nexport type AnthropicMessages = {\n system: string | undefined;\n messages: Anthropic.MessageParam[];\n};\n\nconst ANTHROPIC_IMAGE_MEDIA_TYPES = [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"] as const;\n\ntype AnthropicImageMediaType = (typeof ANTHROPIC_IMAGE_MEDIA_TYPES)[number];\n\n/**\n * Convert vendor-neutral `Message[]` into Anthropic's request shape.\n *\n * Anthropic differs from the OpenAI Chat protocol in three ways this\n * function absorbs:\n *\n * 1. **No `system` role.** System messages are concatenated (newline-\n * separated) and returned separately as the top-level `system`\n * parameter.\n * 2. **Tool results are `user` turns.** A neutral `tool` message becomes\n * a `user` message whose content is a single `tool_result` block\n * keyed by `tool_use_id`.\n * 3. **Tool calls are `tool_use` content blocks.** An assistant message\n * carrying `toolCalls` becomes an `assistant` message whose content\n * is an optional leading `text` block followed by one `tool_use`\n * block per call.\n *\n * Consecutive same-role turns are left as-is — the Messages API merges\n * them server-side.\n *\n * @example\n * const { system, messages } = toAnthropicMessages([\n * { role: \"system\", content: \"Be concise.\" },\n * { role: \"user\", content: \"Hi\" },\n * ]);\n * // system === \"Be concise.\"\n * // messages === [{ role: \"user\", content: \"Hi\" }]\n */\nexport function toAnthropicMessages(messages: Message[]): AnthropicMessages {\n const systemParts: string[] = [];\n const mapped: Anthropic.MessageParam[] = [];\n\n for (const message of messages) {\n if (message.role === \"system\") {\n systemParts.push(stringifyContent(message.content));\n\n continue;\n }\n\n if (message.role === \"tool\") {\n mapped.push({\n role: \"user\",\n content: [\n {\n type: \"tool_result\",\n tool_use_id: message.toolCallId ?? \"\",\n content: stringifyContent(message.content),\n },\n ],\n });\n\n continue;\n }\n\n if (message.role === \"assistant\" && message.toolCalls && message.toolCalls.length > 0) {\n const blocks: Anthropic.ContentBlockParam[] = extractThinkingBlocks(message.toolCalls);\n const text = stringifyContent(message.content);\n\n if (text) {\n blocks.push({ type: \"text\", text });\n }\n\n for (const toolCall of message.toolCalls) {\n blocks.push({\n type: \"tool_use\",\n id: toolCall.id,\n name: toolCall.name,\n input: toolCall.input ?? {},\n });\n }\n\n mapped.push({ role: \"assistant\", content: blocks });\n\n continue;\n }\n\n if (message.role === \"user\" && Array.isArray(message.content)) {\n mapped.push({\n role: \"user\",\n content: message.content.map(toAnthropicContentBlock),\n });\n\n continue;\n }\n\n mapped.push({\n role: message.role === \"assistant\" ? \"assistant\" : \"user\",\n content: stringifyContent(message.content),\n });\n }\n\n return {\n system: systemParts.length > 0 ? systemParts.join(\"\\n\\n\") : undefined,\n messages: mapped,\n };\n}\n\n/**\n * Recover signed extended-thinking blocks stored by this adapter on a tool\n * request. Anthropic requires these blocks to be replayed before the\n * assistant's text and tool uses on the following request.\n */\nfunction extractThinkingBlocks(toolCalls: ModelToolCallRequest[]): Anthropic.ContentBlockParam[] {\n for (const toolCall of toolCalls) {\n const anthropic = toolCall.providerMetadata?.anthropic;\n\n if (!isRecord(anthropic) || !Array.isArray(anthropic.thinkingBlocks)) {\n continue;\n }\n\n return anthropic.thinkingBlocks.flatMap(toAnthropicThinkingBlock);\n }\n\n return [];\n}\n\nfunction toAnthropicThinkingBlock(block: unknown): Anthropic.ContentBlockParam[] {\n if (!isRecord(block) || typeof block.type !== \"string\") {\n return [];\n }\n\n if (\n block.type === \"thinking\" &&\n typeof block.thinking === \"string\" &&\n typeof block.signature === \"string\"\n ) {\n return [{ type: \"thinking\", thinking: block.thinking, signature: block.signature }];\n }\n\n if (block.type === \"redacted_thinking\" && typeof block.data === \"string\") {\n return [{ type: \"redacted_thinking\", data: block.data }];\n }\n\n return [];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/**\n * Multipart content is only meaningful on user messages — for any other\n * role collapse a `ContentPart[]` to its concatenated text so the wire\n * format stays valid. Plain strings pass through unchanged.\n */\nfunction stringifyContent(content: string | ContentPart[]): string {\n if (typeof content === \"string\") {\n return content;\n }\n\n return content\n .filter((part): part is { type: \"text\"; text: string } => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\");\n}\n\n/**\n * Map a single resolved `ContentPart` to an Anthropic content block.\n * Text passes straight through; images become an `image` block with a\n * `url` source (remote) or a `base64` source (inlined bytes). The\n * agent has already resolved every attachment before it reaches here,\n * so this never reads files or fetches URLs.\n */\nfunction toAnthropicContentBlock(part: ContentPart): Anthropic.ContentBlockParam {\n if (part.type === \"text\") {\n return { type: \"text\", text: part.text };\n }\n\n // PDF → Anthropic `document` block (A2). The Messages API accepts a\n // url or base64 `application/pdf` source on PDF-capable Claude models.\n if (part.type === \"pdf\") {\n const source =\n \"url\" in part.source\n ? { type: \"url\", url: part.source.url }\n : { type: \"base64\", media_type: \"application/pdf\", data: part.source.base64 };\n return { type: \"document\", source } as unknown as Anthropic.ContentBlockParam;\n }\n\n // Anthropic has no audio content block; the agent's capability gate\n // normally prevents this, but stay loud rather than mis-map it.\n if (part.type === \"audio\") {\n throw new Error(\"Anthropic adapter: audio attachments are not supported\");\n }\n\n if (\"url\" in part.source) {\n return { type: \"image\", source: { type: \"url\", url: part.source.url } };\n }\n\n return {\n type: \"image\",\n source: {\n type: \"base64\",\n media_type: part.source.mediaType as AnthropicImageMediaType,\n data: part.source.base64,\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"to-anthropic-messages.mjs","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/to-anthropic-messages.ts"],"sourcesContent":["import type { ContentPart, Message, ModelToolCallRequest } from \"@warlock.js/ai\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\nimport { fromProviderBlocks } from \"./provider-blocks\";\n\n/**\n * Result of splitting a vendor-neutral `Message[]` for the Anthropic\n * Messages API: the system prompt is hoisted to a top-level `system`\n * string (Anthropic has no `\"system\"` role inside `messages`), and the\n * remaining turns are mapped to `MessageParam[]`.\n */\nexport type AnthropicMessages = {\n system: string | undefined;\n messages: Anthropic.MessageParam[];\n};\n\n/** Options for {@link toAnthropicMessages}. */\nexport type ToAnthropicMessagesOptions = {\n /**\n * Leave out the `thinking` / `redacted_thinking` blocks of assistant turns\n * marked `compaction: \"retained\"` — turns kept verbatim after a\n * client-written summary. On models that run the preserved-thinking\n * conversation check those blocks were produced against the replaced\n * history, so replaying them is a 400 (or a server-side drop). Text and\n * tool-use blocks stay. Removing them is the no-beta path Anthropic\n * documents for compaction that keeps recent turns.\n */\n dropRetainedThinking?: boolean;\n};\n\nconst ANTHROPIC_IMAGE_MEDIA_TYPES = [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"] as const;\n\ntype AnthropicImageMediaType = (typeof ANTHROPIC_IMAGE_MEDIA_TYPES)[number];\n\n/**\n * Convert vendor-neutral `Message[]` into Anthropic's request shape.\n *\n * Anthropic differs from the OpenAI Chat protocol in three ways this\n * function absorbs:\n *\n * 1. **No `system` role.** System messages are concatenated (newline-\n * separated) and returned separately as the top-level `system`\n * parameter.\n * 2. **Tool results are `user` turns.** A neutral `tool` message becomes\n * a `tool_result` block keyed by `tool_use_id`. Consecutive tool\n * messages (the results of one parallel tool-use turn) are grouped\n * into a single `user` message, as Anthropic expects — splitting them\n * across messages teaches the model to stop calling tools in parallel.\n * 3. **Tool calls are `tool_use` content blocks.** An assistant message\n * carrying `toolCalls` becomes an `assistant` message whose content\n * is an optional leading `text` block followed by one `tool_use`\n * block per call.\n *\n * An assistant message that carries Anthropic `blocks` (thinking,\n * redacted thinking, compaction, server-tool blocks — see\n * `ProviderContentBlock`) is replayed verbatim from those blocks, which\n * keeps signed thinking and compaction state valid. Blocks from another\n * provider, or blocks whose `tool_use` ids no longer match `toolCalls`,\n * are ignored and the turn is rebuilt from `content` + `toolCalls`.\n *\n * With `dropRetainedThinking`, a turn marked `compaction: \"retained\"` is\n * replayed without its thinking blocks (see {@link ToAnthropicMessagesOptions}).\n *\n * Other consecutive same-role turns are left as-is — the Messages API\n * merges them server-side.\n *\n * @example\n * const { system, messages } = toAnthropicMessages([\n * { role: \"system\", content: \"Be concise.\" },\n * { role: \"user\", content: \"Hi\" },\n * ]);\n * // system === \"Be concise.\"\n * // messages === [{ role: \"user\", content: \"Hi\" }]\n */\nexport function toAnthropicMessages(\n messages: Message[],\n options: ToAnthropicMessagesOptions = {},\n): AnthropicMessages {\n const systemParts: string[] = [];\n const mapped: Anthropic.MessageParam[] = [];\n\n for (const message of messages) {\n if (message.role === \"system\") {\n systemParts.push(stringifyContent(message.content));\n\n continue;\n }\n\n if (message.role === \"tool\") {\n const toolResult: Anthropic.ToolResultBlockParam = {\n type: \"tool_result\",\n tool_use_id: message.toolCallId ?? \"\",\n content: stringifyContent(message.content),\n };\n const previous = mapped[mapped.length - 1];\n\n if (previous && previous.role === \"user\" && isToolResultTurn(previous)) {\n previous.content.push(toolResult);\n } else {\n mapped.push({ role: \"user\", content: [toolResult] });\n }\n\n continue;\n }\n\n const dropThinking = options.dropRetainedThinking === true && message.compaction === \"retained\";\n\n if (message.role === \"assistant\") {\n const replayed = fromProviderBlocks(message.blocks, message.toolCalls);\n const content = dropThinking\n ? replayed?.filter((block) => !isThinkingBlock(block))\n : replayed;\n\n if (content && content.length > 0) {\n mapped.push({ role: \"assistant\", content });\n\n continue;\n }\n }\n\n if (message.role === \"assistant\" && message.toolCalls && message.toolCalls.length > 0) {\n const blocks: Anthropic.ContentBlockParam[] = dropThinking\n ? []\n : extractThinkingBlocks(message.toolCalls);\n const text = stringifyContent(message.content);\n\n if (text) {\n blocks.push({ type: \"text\", text });\n }\n\n for (const toolCall of message.toolCalls) {\n blocks.push({\n type: \"tool_use\",\n id: toolCall.id,\n name: toolCall.name,\n input: toolCall.input ?? {},\n });\n }\n\n mapped.push({ role: \"assistant\", content: blocks });\n\n continue;\n }\n\n if (message.role === \"user\" && Array.isArray(message.content)) {\n mapped.push({\n role: \"user\",\n content: message.content.map(toAnthropicContentBlock),\n });\n\n continue;\n }\n\n mapped.push({\n role: message.role === \"assistant\" ? \"assistant\" : \"user\",\n content: stringifyContent(message.content),\n });\n }\n\n return {\n system: systemParts.length > 0 ? systemParts.join(\"\\n\\n\") : undefined,\n messages: mapped,\n };\n}\n\nfunction isThinkingBlock(block: Anthropic.ContentBlockParam): boolean {\n return block.type === \"thinking\" || block.type === \"redacted_thinking\";\n}\n\n/**\n * Recover signed extended-thinking blocks stored by this adapter on a tool\n * request. Anthropic requires these blocks to be replayed before the\n * assistant's text and tool uses on the following request.\n */\nfunction extractThinkingBlocks(toolCalls: ModelToolCallRequest[]): Anthropic.ContentBlockParam[] {\n for (const toolCall of toolCalls) {\n const anthropic = toolCall.providerMetadata?.anthropic;\n\n if (!isRecord(anthropic) || !Array.isArray(anthropic.thinkingBlocks)) {\n continue;\n }\n\n return anthropic.thinkingBlocks.flatMap(toAnthropicThinkingBlock);\n }\n\n return [];\n}\n\nfunction toAnthropicThinkingBlock(block: unknown): Anthropic.ContentBlockParam[] {\n if (!isRecord(block) || typeof block.type !== \"string\") {\n return [];\n }\n\n if (\n block.type === \"thinking\" &&\n typeof block.thinking === \"string\" &&\n typeof block.signature === \"string\"\n ) {\n return [{ type: \"thinking\", thinking: block.thinking, signature: block.signature }];\n }\n\n if (block.type === \"redacted_thinking\" && typeof block.data === \"string\") {\n return [{ type: \"redacted_thinking\", data: block.data }];\n }\n\n return [];\n}\n\n/**\n * A `user` turn this function built from tool results — its content is a\n * non-empty array made only of `tool_result` blocks. Later tool results\n * of the same tool-use turn are appended to it.\n */\nfunction isToolResultTurn(\n message: Anthropic.MessageParam,\n): message is Anthropic.MessageParam & { content: Anthropic.ContentBlockParam[] } {\n return (\n Array.isArray(message.content) &&\n message.content.length > 0 &&\n message.content.every((block) => block.type === \"tool_result\")\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/**\n * Multipart content is only meaningful on user messages — for any other\n * role collapse a `ContentPart[]` to its concatenated text so the wire\n * format stays valid. Plain strings pass through unchanged.\n */\nfunction stringifyContent(content: string | ContentPart[]): string {\n if (typeof content === \"string\") {\n return content;\n }\n\n return content\n .filter((part): part is { type: \"text\"; text: string } => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\");\n}\n\n/**\n * Map a single resolved `ContentPart` to an Anthropic content block.\n * Text passes straight through; images become an `image` block with a\n * `url` source (remote) or a `base64` source (inlined bytes). The\n * agent has already resolved every attachment before it reaches here,\n * so this never reads files or fetches URLs.\n */\nfunction toAnthropicContentBlock(part: ContentPart): Anthropic.ContentBlockParam {\n if (part.type === \"text\") {\n return { type: \"text\", text: part.text };\n }\n\n // PDF → Anthropic `document` block (A2). The Messages API accepts a\n // url or base64 `application/pdf` source on PDF-capable Claude models.\n if (part.type === \"pdf\") {\n const source =\n \"url\" in part.source\n ? { type: \"url\", url: part.source.url }\n : { type: \"base64\", media_type: \"application/pdf\", data: part.source.base64 };\n return { type: \"document\", source } as unknown as Anthropic.ContentBlockParam;\n }\n\n // Anthropic has no audio content block; the agent's capability gate\n // normally prevents this, but stay loud rather than mis-map it.\n if (part.type === \"audio\") {\n throw new Error(\"Anthropic adapter: audio attachments are not supported\");\n }\n\n if (\"url\" in part.source) {\n return { type: \"image\", source: { type: \"url\", url: part.source.url } };\n }\n\n return {\n type: \"image\",\n source: {\n type: \"base64\",\n media_type: part.source.mediaType as AnthropicImageMediaType,\n data: part.source.base64,\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { ToolConfig } from "@warlock.js/ai";
|
|
3
|
+
|
|
4
|
+
//#region ../../packages/ai-anthropic/src/utils/to-anthropic-tools.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Convert vendor-neutral `ToolConfig[]` into Anthropic's `tools` array.
|
|
7
|
+
* Uses the shared `extractJsonSchema` helper; Anthropic requires the
|
|
8
|
+
* input schema to be a JSON-Schema object, so a non-object extraction
|
|
9
|
+
* is coerced into an empty-object schema rather than rejected — the
|
|
10
|
+
* tool still registers and the model simply sees no parameters.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const tools = toAnthropicTools([weatherTool, calculatorTool]);
|
|
14
|
+
* await client.messages.create({ model, max_tokens, messages, tools });
|
|
15
|
+
*/
|
|
16
|
+
declare function toAnthropicTools(tools: ToolConfig<unknown, unknown>[] | undefined): Anthropic.Tool[] | undefined;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { toAnthropicTools };
|
|
19
|
+
//# sourceMappingURL=to-anthropic-tools.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"to-anthropic-tools.d.mts","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/to-anthropic-tools.ts"],"mappings":";;;;;;AAcA;;;;;;;;;iBAAgB,gBAAA,CACd,KAAA,EAAO,UAAA,mCACN,SAAA,CAAU,IAAI"}
|