@librechat/agents 3.2.39 → 3.2.42
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/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +4 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +4 -3
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +9 -1
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +4 -0
- package/dist/cjs/messages/cache.cjs +21 -0
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/tools/ReadFile.cjs +2 -2
- package/dist/cjs/tools/ReadFile.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs +13 -13
- package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs +87 -7
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalCodingTools.cjs +11 -11
- package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +5 -2
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs +4 -3
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +10 -2
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/main.mjs +3 -3
- package/dist/esm/messages/cache.mjs +21 -1
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/tools/ReadFile.mjs +2 -2
- package/dist/esm/tools/ReadFile.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs +14 -14
- package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs +85 -8
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs.map +1 -1
- package/dist/esm/tools/local/LocalCodingTools.mjs +11 -11
- package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
- package/dist/types/llm/bedrock/index.d.ts +7 -0
- package/dist/types/messages/cache.d.ts +17 -0
- package/dist/types/tools/ReadFile.d.ts +4 -4
- package/dist/types/tools/cloudflare/CloudflareSandboxExecutionEngine.d.ts +44 -0
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +2 -0
- package/src/graphs/Graph.ts +17 -5
- package/src/hooks/__tests__/createWorkspacePolicyHook.test.ts +12 -12
- package/src/hooks/createWorkspacePolicyHook.ts +7 -6
- package/src/llm/bedrock/index.ts +18 -2
- package/src/llm/bedrock/llm.spec.ts +97 -0
- package/src/messages/cache.test.ts +31 -0
- package/src/messages/cache.ts +25 -0
- package/src/tools/ReadFile.ts +2 -2
- package/src/tools/__tests__/CloudflareSandboxExecution.test.ts +131 -0
- package/src/tools/__tests__/LocalExecutionTools.test.ts +25 -25
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +5 -5
- package/src/tools/__tests__/ReadFile.test.ts +3 -3
- package/src/tools/__tests__/ToolNode.session.test.ts +2 -2
- package/src/tools/__tests__/workspaceSeam.test.ts +2 -2
- package/src/tools/cloudflare/CloudflareProgrammaticToolCalling.ts +18 -13
- package/src/tools/cloudflare/CloudflareSandboxExecutionEngine.ts +165 -9
- package/src/tools/local/LocalCodingTools.ts +14 -14
|
@@ -75,10 +75,11 @@ function extractCompileCheckPaths(input) {
|
|
|
75
75
|
for (const match of command.matchAll(ABSOLUTE_PATH_TOKEN)) out.push(expandHomeRelative(match[1]));
|
|
76
76
|
return out;
|
|
77
77
|
}
|
|
78
|
+
const extractPath = (i) => typeof i.path === "string" && i.path !== "" ? [i.path] : [];
|
|
78
79
|
const DEFAULT_EXTRACTORS = {
|
|
79
|
-
["read_file"]:
|
|
80
|
-
["write_file"]:
|
|
81
|
-
["edit_file"]:
|
|
80
|
+
["read_file"]: extractPath,
|
|
81
|
+
["write_file"]: extractPath,
|
|
82
|
+
["edit_file"]: extractPath,
|
|
82
83
|
["grep_search"]: (i) => typeof i.path === "string" && i.path !== "" ? [i.path] : [],
|
|
83
84
|
["glob_search"]: (i) => typeof i.path === "string" && i.path !== "" ? [i.path] : [],
|
|
84
85
|
["list_directory"]: (i) => typeof i.path === "string" && i.path !== "" ? [i.path] : [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createWorkspacePolicyHook.cjs","names":[],"sources":["../../../src/hooks/createWorkspacePolicyHook.ts"],"sourcesContent":["/**\n * Workspace boundary policy as a `PreToolUse` hook.\n *\n * Local-engine file tools enforce a hard workspace boundary at the\n * tool implementation layer (`resolveWorkspacePathSafe`). This hook\n * adds a complementary, host-controlled layer on top that uses the\n * standard PreToolUse / HITL machinery to *negotiate* access to\n * paths outside the workspace — instead of just throwing.\n *\n * The host opts in by registering this hook on a `HookRegistry`; the\n * hook inspects each tool call's input, extracts the file paths it\n * mentions via per-tool extractors, and returns:\n *\n * - `allow` — every path is inside `workspace.root`\n * (or `additionalRoots`)\n * - `deny` — at least one path is outside, and the\n * configured outside-policy is `'deny'`\n * - `ask` — at least one path is outside, and the\n * outside-policy is `'ask'` (default).\n * When `humanInTheLoop.enabled` is true,\n * the existing PreToolUse `'ask'` flow\n * raises a tool_approval interrupt the\n * host UI can render. When HITL is off,\n * `'ask'` collapses to `deny` (matches\n * the rest of the SDK's default).\n *\n * Default per-tool path extractors cover the local-engine coding\n * suite (`read_file`, `write_file`, `edit_file`, `grep_search`,\n * `glob_search`, `list_directory`, `compile_check`). The host can\n * override or extend via `pathExtractors`. Bash/code paths are not\n * extracted by default — bash command parsing is its own concern, and\n * the existing `bashAst` validator + sandbox-runtime fs allowlist are\n * the right gates for those.\n *\n * Important: this hook does NOT replace `resolveWorkspacePathSafe`.\n * Even if the hook returns `allow`, the file tool still enforces its\n * own clamp unless `workspace.allowReadOutside` /\n * `workspace.allowWriteOutside` (or the legacy\n * `allowOutsideWorkspace`) is set. The recommended composition for\n * \"ask the user\" semantics is:\n *\n * workspace: {\n * root,\n * allowReadOutside: true,\n * allowWriteOutside: true,\n * },\n * // …with the hook installed and humanInTheLoop.enabled = true.\n */\n\nimport { homedir } from 'os';\nimport { realpath } from 'fs/promises';\nimport { isAbsolute, relative, resolve } from 'path';\nimport type {\n HookCallback,\n PreToolUseHookInput,\n PreToolUseHookOutput,\n ToolDecision,\n} from './types';\nimport { Constants } from '@/common';\n\n/**\n * What to do when a tool call references a path outside the workspace.\n *\n * - `'ask'` : default. Raise a PreToolUse `ask` (host UI prompts\n * via the HITL interrupt path).\n * - `'allow'` : let the call through (use the existing tool clamp\n * to actually enforce — the hook is purely advisory).\n * - `'deny'` : block the call with an error ToolMessage.\n */\nexport type OutsideAccessPolicy = 'ask' | 'allow' | 'deny';\n\nexport interface WorkspacePolicyConfig {\n /** Canonical workspace root. Required. */\n root: string;\n /** Sibling roots that count as inside-workspace. */\n additionalRoots?: readonly string[];\n /** Policy applied to read-only file tools. Defaults to `'ask'`. */\n outsideRead?: OutsideAccessPolicy;\n /** Policy applied to write-shaped file tools. Defaults to `'ask'`. */\n outsideWrite?: OutsideAccessPolicy;\n /**\n * Optional reason template surfaced in the `ask`/`deny` decision.\n * Supports `{tool}` and `{paths}` substitution.\n */\n reason?: string;\n /**\n * Per-tool path extractors. Defaults cover the local-engine coding\n * suite. Returning an empty array opts that tool out of policy.\n */\n pathExtractors?: Record<string, PathExtractor>;\n}\n\nexport type PathExtractor = (\n toolInput: Record<string, unknown>\n) => readonly string[];\n\nconst READ_TOOLS = new Set<string>([\n Constants.READ_FILE,\n Constants.GREP_SEARCH,\n Constants.GLOB_SEARCH,\n Constants.LIST_DIRECTORY,\n Constants.COMPILE_CHECK,\n]);\n\nconst WRITE_TOOLS = new Set<string>([\n Constants.WRITE_FILE,\n Constants.EDIT_FILE,\n]);\n\n/**\n * Best-effort extractor for `compile_check` — pulls absolute and `~/`\n * path tokens out of the `command` string so the workspace boundary\n * sees them. Without this, a model could ship `command: 'cat\n * /etc/passwd'` and the policy hook would short-circuit to `allow`\n * (Codex P1 #26 — the prior `() => []` made the hook a no-op for\n * compile_check). Conservative by design:\n *\n * - Matches `/foo`, `~/foo`, `$HOME/foo`, `${HOME}/foo` followed by\n * non-shell-special chars. Stops at whitespace, quotes, redirect\n * operators, pipes, semicolons.\n * - Strips a leading `--flag=` so `--out=/etc/foo` extracts as\n * `/etc/foo` (the path the agent's actually trying to write).\n * - Misses relative paths (intended — those resolve under cwd\n * anyway), and shell-substituted paths whose final form isn't\n * visible at extract time. Hosts that need bulletproof gating\n * should pair this with a `bash_tool`-level policy.\n */\n// `[\"']?` slots before AND after the captured path cover quoted\n// forms like `cat \"/etc/passwd\"` and `--out='/tmp/x'`. Codex P1 #31\n// — the previous regex only matched unquoted tokens, so a model\n// could trivially bypass the workspace policy by quoting any\n// destination path. The path content character class still excludes\n// quotes/whitespace/shell-specials so we don't over-extract; that's\n// the defensive trade we want for fallback-grep style matching.\n//\n// The `\\.\\.(?:\\/[^…]*)?` alternation covers parent-traversal forms\n// (`..`, `../secrets.txt`, `../foo/bar`). Without it, a model could\n// exfiltrate parent-directory files via `cat ../secrets` and the\n// hook would short-circuit to `allow` because the extractor saw no\n// \"absolute\" token. The boundary check at the call site resolves\n// non-absolute extracted tokens against `root`, so `../secrets`\n// becomes `<parent-of-workspace>/secrets` which the boundary then\n// correctly flags as outside. Codex P2 #35.\nconst PATH_TOKEN =\n /(?:^|[\\s=])(?:--[^\\s=]+=)?[\"']?(\\/[^\\s'\"|;&<>()`]+|~\\/[^\\s'\"|;&<>()`]+|\\$\\{?HOME\\}?\\/[^\\s'\"|;&<>()`]+|\\.\\.(?:\\/[^\\s'\"|;&<>()`]*)?)[\"']?/g;\n// Back-compat alias kept for any downstream import.\nconst ABSOLUTE_PATH_TOKEN = PATH_TOKEN;\nfunction expandHomeRelative(token: string): string {\n // Expand ~/foo and $HOME/foo and ${HOME}/foo to absolute. The\n // workspace boundary check resolves non-absolute paths against the\n // workspace root, which would silently treat `~/secret` as\n // `<workspace>/~/secret` — exactly the bypass the codex flagged.\n const home = homedir();\n if (token.startsWith('~/')) return `${home}/${token.slice(2)}`;\n if (token.startsWith('${HOME}/')) return `${home}/${token.slice(8)}`;\n if (token.startsWith('$HOME/')) return `${home}/${token.slice(6)}`;\n return token;\n}\nfunction extractCompileCheckPaths(input: Record<string, unknown>): string[] {\n const command = typeof input.command === 'string' ? input.command : '';\n if (command === '') return [];\n const out: string[] = [];\n for (const match of command.matchAll(ABSOLUTE_PATH_TOKEN)) {\n out.push(expandHomeRelative(match[1]));\n }\n return out;\n}\n\nconst DEFAULT_EXTRACTORS: Record<string, PathExtractor> = {\n [Constants.READ_FILE]: (i) =>\n typeof i.file_path === 'string' ? [i.file_path] : [],\n [Constants.WRITE_FILE]: (i) =>\n typeof i.file_path === 'string' ? [i.file_path] : [],\n [Constants.EDIT_FILE]: (i) =>\n typeof i.file_path === 'string' ? [i.file_path] : [],\n [Constants.GREP_SEARCH]: (i) =>\n typeof i.path === 'string' && i.path !== '' ? [i.path] : [],\n [Constants.GLOB_SEARCH]: (i) =>\n typeof i.path === 'string' && i.path !== '' ? [i.path] : [],\n [Constants.LIST_DIRECTORY]: (i) =>\n typeof i.path === 'string' && i.path !== '' ? [i.path] : [],\n [Constants.COMPILE_CHECK]: extractCompileCheckPaths,\n};\n\nfunction isInsideAnyRoot(absolutePath: string, roots: string[]): boolean {\n for (const root of roots) {\n if (absolutePath === root) return true;\n const rel = relative(root, absolutePath);\n if (!rel.startsWith('..') && !isAbsolute(rel)) return true;\n }\n return false;\n}\n\n/**\n * Symlink-aware variant: realpaths the candidate AND the roots before\n * comparing. Without this, a symlink inside the workspace pointing\n * outside (e.g. `workspace/link → /etc/passwd`) compares as\n * \"in-workspace\" lexically, but actually grants the agent reach\n * outside the boundary. Critical when this hook is the primary gate\n * (i.e. the host opted into `workspace.allowReadOutside: true` /\n * `allowWriteOutside: true` so the file tools' own clamp is off).\n *\n * Handles paths that don't yet exist (e.g. `write_file` to a brand\n * new path) by walking up to the nearest existing ancestor and\n * realpathing that, then re-attaching the unresolved suffix. Mirrors\n * `resolveWorkspacePathSafe`'s approach in LocalExecutionEngine.\n */\nasync function realpathOrSelf(absolutePath: string): Promise<string> {\n try {\n return await realpath(absolutePath);\n } catch {\n return absolutePath;\n }\n}\n\nasync function realpathOfPathOrAncestor(absolutePath: string): Promise<string> {\n let current = absolutePath;\n let suffix = '';\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n try {\n const real = await realpath(current);\n return suffix === '' ? real : resolve(real, suffix);\n } catch {\n const parent = resolve(current, '..');\n if (parent === current) {\n return absolutePath;\n }\n const base = current.slice(parent.length + 1);\n suffix = suffix === '' ? base : `${base}/${suffix}`;\n current = parent;\n }\n }\n}\n\nasync function isInsideAnyRootRealpath(\n absolutePath: string,\n realRoots: readonly string[]\n): Promise<boolean> {\n const real = await realpathOfPathOrAncestor(absolutePath);\n return isInsideAnyRoot(real, [...realRoots]);\n}\n\nfunction formatReason(\n template: string | undefined,\n toolName: string,\n outsidePaths: readonly string[]\n): string {\n const fallback = `Tool \"${toolName}\" wants to touch ${outsidePaths.length} path(s) outside the workspace: ${outsidePaths.join(', ')}`;\n if (template == null) return fallback;\n return template\n .replace(/\\{tool\\}/g, toolName)\n .replace(/\\{paths\\}/g, outsidePaths.join(', '));\n}\n\n/**\n * Build a `PreToolUse` callback that enforces the workspace policy.\n * Register it on a `HookRegistry`:\n *\n * ```ts\n * registry.register('PreToolUse', {\n * hooks: [createWorkspacePolicyHook({ root, outsideWrite: 'ask' })],\n * });\n * ```\n *\n * The hook is composable with `createToolPolicyHook` — register both;\n * `executeHooks` precedence (`deny > ask > allow`) sorts out which\n * decision wins per call.\n */\nexport function createWorkspacePolicyHook(\n config: WorkspacePolicyConfig\n): HookCallback<'PreToolUse'> {\n const root = resolve(config.root);\n // Relative `additionalRoots` entries are anchored to `root` so a\n // monorepo config like `additionalRoots: ['../shared']` resolves\n // to a sibling of `root`, not of process.cwd. Matches\n // `getWorkspaceRoots` in LocalExecutionEngine.\n const additionalRoots = (config.additionalRoots ?? []).map((p) =>\n isAbsolute(p) ? resolve(p) : resolve(root, p)\n );\n const allRoots = [root, ...additionalRoots];\n\n // Pre-realpath the roots once at construction — these are stable\n // per Run. The candidate paths get realpath'd lazily inside the\n // hook callback. Cached so the per-call cost is just one realpath.\n let realRootsPromise: Promise<string[]> | undefined;\n const getRealRoots = (): Promise<string[]> => {\n if (realRootsPromise == null) {\n realRootsPromise = Promise.all(allRoots.map(realpathOrSelf));\n }\n return realRootsPromise;\n };\n\n const readPolicy: OutsideAccessPolicy = config.outsideRead ?? 'ask';\n const writePolicy: OutsideAccessPolicy = config.outsideWrite ?? 'ask';\n\n const extractors: Record<string, PathExtractor | undefined> = {\n ...DEFAULT_EXTRACTORS,\n ...(config.pathExtractors ?? {}),\n };\n\n /** Unknown tools are treated as writes (the stricter policy). */\n const resolvePolicy = (toolName: string): OutsideAccessPolicy => {\n if (WRITE_TOOLS.has(toolName)) {\n return writePolicy;\n }\n if (READ_TOOLS.has(toolName)) {\n return readPolicy;\n }\n return writePolicy;\n };\n\n return async (input: PreToolUseHookInput): Promise<PreToolUseHookOutput> => {\n const extractor = extractors[input.toolName];\n if (extractor == null) return { decision: 'allow' };\n\n const paths = extractor(input.toolInput);\n if (paths.length === 0) return { decision: 'allow' };\n\n // Two-stage check:\n // 1. Lexical fast path — anything that's lexically inside the\n // workspace AND doesn't get redirected by realpath stays\n // allow-able without paying the realpath cost on every call.\n // 2. For paths that look outside lexically OR look inside but\n // may have been routed through a symlink, realpath both the\n // candidate and the roots and compare. This catches the\n // `workspace/link → /etc/passwd` escape that lexical-only\n // checks miss.\n const outside: string[] = [];\n const realRoots = await getRealRoots();\n for (const p of paths) {\n const abs = isAbsolute(p) ? resolve(p) : resolve(root, p);\n // Realpath is the source of truth — it catches both the\n // symlink-escape case (lexically-inside path that resolves\n // outside) and the alternate-mount case (lexically-outside\n // path that resolves back inside the workspace). The lexical\n // check alone gives the wrong answer for either, so we don't\n // bother computing it.\n const realInside = await isInsideAnyRootRealpath(abs, realRoots);\n if (!realInside) {\n outside.push(p);\n }\n }\n if (outside.length === 0) return { decision: 'allow' };\n\n const policy = resolvePolicy(input.toolName);\n if (policy === 'allow') return { decision: 'allow' };\n\n const decision: ToolDecision = policy === 'deny' ? 'deny' : 'ask';\n return {\n decision,\n reason: formatReason(config.reason, input.toolName, outside),\n ...(decision === 'ask'\n ? { allowedDecisions: ['approve', 'reject'] as const }\n : {}),\n };\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"createWorkspacePolicyHook.cjs","names":[],"sources":["../../../src/hooks/createWorkspacePolicyHook.ts"],"sourcesContent":["/**\n * Workspace boundary policy as a `PreToolUse` hook.\n *\n * Local-engine file tools enforce a hard workspace boundary at the\n * tool implementation layer (`resolveWorkspacePathSafe`). This hook\n * adds a complementary, host-controlled layer on top that uses the\n * standard PreToolUse / HITL machinery to *negotiate* access to\n * paths outside the workspace — instead of just throwing.\n *\n * The host opts in by registering this hook on a `HookRegistry`; the\n * hook inspects each tool call's input, extracts the file paths it\n * mentions via per-tool extractors, and returns:\n *\n * - `allow` — every path is inside `workspace.root`\n * (or `additionalRoots`)\n * - `deny` — at least one path is outside, and the\n * configured outside-policy is `'deny'`\n * - `ask` — at least one path is outside, and the\n * outside-policy is `'ask'` (default).\n * When `humanInTheLoop.enabled` is true,\n * the existing PreToolUse `'ask'` flow\n * raises a tool_approval interrupt the\n * host UI can render. When HITL is off,\n * `'ask'` collapses to `deny` (matches\n * the rest of the SDK's default).\n *\n * Default per-tool path extractors cover the local-engine coding\n * suite (`read_file`, `write_file`, `edit_file`, `grep_search`,\n * `glob_search`, `list_directory`, `compile_check`). The host can\n * override or extend via `pathExtractors`. Bash/code paths are not\n * extracted by default — bash command parsing is its own concern, and\n * the existing `bashAst` validator + sandbox-runtime fs allowlist are\n * the right gates for those.\n *\n * Important: this hook does NOT replace `resolveWorkspacePathSafe`.\n * Even if the hook returns `allow`, the file tool still enforces its\n * own clamp unless `workspace.allowReadOutside` /\n * `workspace.allowWriteOutside` (or the legacy\n * `allowOutsideWorkspace`) is set. The recommended composition for\n * \"ask the user\" semantics is:\n *\n * workspace: {\n * root,\n * allowReadOutside: true,\n * allowWriteOutside: true,\n * },\n * // …with the hook installed and humanInTheLoop.enabled = true.\n */\n\nimport { homedir } from 'os';\nimport { realpath } from 'fs/promises';\nimport { isAbsolute, relative, resolve } from 'path';\nimport type {\n HookCallback,\n PreToolUseHookInput,\n PreToolUseHookOutput,\n ToolDecision,\n} from './types';\nimport { Constants } from '@/common';\n\n/**\n * What to do when a tool call references a path outside the workspace.\n *\n * - `'ask'` : default. Raise a PreToolUse `ask` (host UI prompts\n * via the HITL interrupt path).\n * - `'allow'` : let the call through (use the existing tool clamp\n * to actually enforce — the hook is purely advisory).\n * - `'deny'` : block the call with an error ToolMessage.\n */\nexport type OutsideAccessPolicy = 'ask' | 'allow' | 'deny';\n\nexport interface WorkspacePolicyConfig {\n /** Canonical workspace root. Required. */\n root: string;\n /** Sibling roots that count as inside-workspace. */\n additionalRoots?: readonly string[];\n /** Policy applied to read-only file tools. Defaults to `'ask'`. */\n outsideRead?: OutsideAccessPolicy;\n /** Policy applied to write-shaped file tools. Defaults to `'ask'`. */\n outsideWrite?: OutsideAccessPolicy;\n /**\n * Optional reason template surfaced in the `ask`/`deny` decision.\n * Supports `{tool}` and `{paths}` substitution.\n */\n reason?: string;\n /**\n * Per-tool path extractors. Defaults cover the local-engine coding\n * suite. Returning an empty array opts that tool out of policy.\n */\n pathExtractors?: Record<string, PathExtractor>;\n}\n\nexport type PathExtractor = (\n toolInput: Record<string, unknown>\n) => readonly string[];\n\nconst READ_TOOLS = new Set<string>([\n Constants.READ_FILE,\n Constants.GREP_SEARCH,\n Constants.GLOB_SEARCH,\n Constants.LIST_DIRECTORY,\n Constants.COMPILE_CHECK,\n]);\n\nconst WRITE_TOOLS = new Set<string>([\n Constants.WRITE_FILE,\n Constants.EDIT_FILE,\n]);\n\n/**\n * Best-effort extractor for `compile_check` — pulls absolute and `~/`\n * path tokens out of the `command` string so the workspace boundary\n * sees them. Without this, a model could ship `command: 'cat\n * /etc/passwd'` and the policy hook would short-circuit to `allow`\n * (Codex P1 #26 — the prior `() => []` made the hook a no-op for\n * compile_check). Conservative by design:\n *\n * - Matches `/foo`, `~/foo`, `$HOME/foo`, `${HOME}/foo` followed by\n * non-shell-special chars. Stops at whitespace, quotes, redirect\n * operators, pipes, semicolons.\n * - Strips a leading `--flag=` so `--out=/etc/foo` extracts as\n * `/etc/foo` (the path the agent's actually trying to write).\n * - Misses relative paths (intended — those resolve under cwd\n * anyway), and shell-substituted paths whose final form isn't\n * visible at extract time. Hosts that need bulletproof gating\n * should pair this with a `bash_tool`-level policy.\n */\n// `[\"']?` slots before AND after the captured path cover quoted\n// forms like `cat \"/etc/passwd\"` and `--out='/tmp/x'`. Codex P1 #31\n// — the previous regex only matched unquoted tokens, so a model\n// could trivially bypass the workspace policy by quoting any\n// destination path. The path content character class still excludes\n// quotes/whitespace/shell-specials so we don't over-extract; that's\n// the defensive trade we want for fallback-grep style matching.\n//\n// The `\\.\\.(?:\\/[^…]*)?` alternation covers parent-traversal forms\n// (`..`, `../secrets.txt`, `../foo/bar`). Without it, a model could\n// exfiltrate parent-directory files via `cat ../secrets` and the\n// hook would short-circuit to `allow` because the extractor saw no\n// \"absolute\" token. The boundary check at the call site resolves\n// non-absolute extracted tokens against `root`, so `../secrets`\n// becomes `<parent-of-workspace>/secrets` which the boundary then\n// correctly flags as outside. Codex P2 #35.\nconst PATH_TOKEN =\n /(?:^|[\\s=])(?:--[^\\s=]+=)?[\"']?(\\/[^\\s'\"|;&<>()`]+|~\\/[^\\s'\"|;&<>()`]+|\\$\\{?HOME\\}?\\/[^\\s'\"|;&<>()`]+|\\.\\.(?:\\/[^\\s'\"|;&<>()`]*)?)[\"']?/g;\n// Back-compat alias kept for any downstream import.\nconst ABSOLUTE_PATH_TOKEN = PATH_TOKEN;\nfunction expandHomeRelative(token: string): string {\n // Expand ~/foo and $HOME/foo and ${HOME}/foo to absolute. The\n // workspace boundary check resolves non-absolute paths against the\n // workspace root, which would silently treat `~/secret` as\n // `<workspace>/~/secret` — exactly the bypass the codex flagged.\n const home = homedir();\n if (token.startsWith('~/')) return `${home}/${token.slice(2)}`;\n if (token.startsWith('${HOME}/')) return `${home}/${token.slice(8)}`;\n if (token.startsWith('$HOME/')) return `${home}/${token.slice(6)}`;\n return token;\n}\nfunction extractCompileCheckPaths(input: Record<string, unknown>): string[] {\n const command = typeof input.command === 'string' ? input.command : '';\n if (command === '') return [];\n const out: string[] = [];\n for (const match of command.matchAll(ABSOLUTE_PATH_TOKEN)) {\n out.push(expandHomeRelative(match[1]));\n }\n return out;\n}\n\n// All built-in coding tools take their file/dir target in `path`.\nconst extractPath: PathExtractor = (i) =>\n typeof i.path === 'string' && i.path !== '' ? [i.path] : [];\n\nconst DEFAULT_EXTRACTORS: Record<string, PathExtractor> = {\n [Constants.READ_FILE]: extractPath,\n [Constants.WRITE_FILE]: extractPath,\n [Constants.EDIT_FILE]: extractPath,\n [Constants.GREP_SEARCH]: (i) =>\n typeof i.path === 'string' && i.path !== '' ? [i.path] : [],\n [Constants.GLOB_SEARCH]: (i) =>\n typeof i.path === 'string' && i.path !== '' ? [i.path] : [],\n [Constants.LIST_DIRECTORY]: (i) =>\n typeof i.path === 'string' && i.path !== '' ? [i.path] : [],\n [Constants.COMPILE_CHECK]: extractCompileCheckPaths,\n};\n\nfunction isInsideAnyRoot(absolutePath: string, roots: string[]): boolean {\n for (const root of roots) {\n if (absolutePath === root) return true;\n const rel = relative(root, absolutePath);\n if (!rel.startsWith('..') && !isAbsolute(rel)) return true;\n }\n return false;\n}\n\n/**\n * Symlink-aware variant: realpaths the candidate AND the roots before\n * comparing. Without this, a symlink inside the workspace pointing\n * outside (e.g. `workspace/link → /etc/passwd`) compares as\n * \"in-workspace\" lexically, but actually grants the agent reach\n * outside the boundary. Critical when this hook is the primary gate\n * (i.e. the host opted into `workspace.allowReadOutside: true` /\n * `allowWriteOutside: true` so the file tools' own clamp is off).\n *\n * Handles paths that don't yet exist (e.g. `write_file` to a brand\n * new path) by walking up to the nearest existing ancestor and\n * realpathing that, then re-attaching the unresolved suffix. Mirrors\n * `resolveWorkspacePathSafe`'s approach in LocalExecutionEngine.\n */\nasync function realpathOrSelf(absolutePath: string): Promise<string> {\n try {\n return await realpath(absolutePath);\n } catch {\n return absolutePath;\n }\n}\n\nasync function realpathOfPathOrAncestor(absolutePath: string): Promise<string> {\n let current = absolutePath;\n let suffix = '';\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n try {\n const real = await realpath(current);\n return suffix === '' ? real : resolve(real, suffix);\n } catch {\n const parent = resolve(current, '..');\n if (parent === current) {\n return absolutePath;\n }\n const base = current.slice(parent.length + 1);\n suffix = suffix === '' ? base : `${base}/${suffix}`;\n current = parent;\n }\n }\n}\n\nasync function isInsideAnyRootRealpath(\n absolutePath: string,\n realRoots: readonly string[]\n): Promise<boolean> {\n const real = await realpathOfPathOrAncestor(absolutePath);\n return isInsideAnyRoot(real, [...realRoots]);\n}\n\nfunction formatReason(\n template: string | undefined,\n toolName: string,\n outsidePaths: readonly string[]\n): string {\n const fallback = `Tool \"${toolName}\" wants to touch ${outsidePaths.length} path(s) outside the workspace: ${outsidePaths.join(', ')}`;\n if (template == null) return fallback;\n return template\n .replace(/\\{tool\\}/g, toolName)\n .replace(/\\{paths\\}/g, outsidePaths.join(', '));\n}\n\n/**\n * Build a `PreToolUse` callback that enforces the workspace policy.\n * Register it on a `HookRegistry`:\n *\n * ```ts\n * registry.register('PreToolUse', {\n * hooks: [createWorkspacePolicyHook({ root, outsideWrite: 'ask' })],\n * });\n * ```\n *\n * The hook is composable with `createToolPolicyHook` — register both;\n * `executeHooks` precedence (`deny > ask > allow`) sorts out which\n * decision wins per call.\n */\nexport function createWorkspacePolicyHook(\n config: WorkspacePolicyConfig\n): HookCallback<'PreToolUse'> {\n const root = resolve(config.root);\n // Relative `additionalRoots` entries are anchored to `root` so a\n // monorepo config like `additionalRoots: ['../shared']` resolves\n // to a sibling of `root`, not of process.cwd. Matches\n // `getWorkspaceRoots` in LocalExecutionEngine.\n const additionalRoots = (config.additionalRoots ?? []).map((p) =>\n isAbsolute(p) ? resolve(p) : resolve(root, p)\n );\n const allRoots = [root, ...additionalRoots];\n\n // Pre-realpath the roots once at construction — these are stable\n // per Run. The candidate paths get realpath'd lazily inside the\n // hook callback. Cached so the per-call cost is just one realpath.\n let realRootsPromise: Promise<string[]> | undefined;\n const getRealRoots = (): Promise<string[]> => {\n if (realRootsPromise == null) {\n realRootsPromise = Promise.all(allRoots.map(realpathOrSelf));\n }\n return realRootsPromise;\n };\n\n const readPolicy: OutsideAccessPolicy = config.outsideRead ?? 'ask';\n const writePolicy: OutsideAccessPolicy = config.outsideWrite ?? 'ask';\n\n const extractors: Record<string, PathExtractor | undefined> = {\n ...DEFAULT_EXTRACTORS,\n ...(config.pathExtractors ?? {}),\n };\n\n /** Unknown tools are treated as writes (the stricter policy). */\n const resolvePolicy = (toolName: string): OutsideAccessPolicy => {\n if (WRITE_TOOLS.has(toolName)) {\n return writePolicy;\n }\n if (READ_TOOLS.has(toolName)) {\n return readPolicy;\n }\n return writePolicy;\n };\n\n return async (input: PreToolUseHookInput): Promise<PreToolUseHookOutput> => {\n const extractor = extractors[input.toolName];\n if (extractor == null) return { decision: 'allow' };\n\n const paths = extractor(input.toolInput);\n if (paths.length === 0) return { decision: 'allow' };\n\n // Two-stage check:\n // 1. Lexical fast path — anything that's lexically inside the\n // workspace AND doesn't get redirected by realpath stays\n // allow-able without paying the realpath cost on every call.\n // 2. For paths that look outside lexically OR look inside but\n // may have been routed through a symlink, realpath both the\n // candidate and the roots and compare. This catches the\n // `workspace/link → /etc/passwd` escape that lexical-only\n // checks miss.\n const outside: string[] = [];\n const realRoots = await getRealRoots();\n for (const p of paths) {\n const abs = isAbsolute(p) ? resolve(p) : resolve(root, p);\n // Realpath is the source of truth — it catches both the\n // symlink-escape case (lexically-inside path that resolves\n // outside) and the alternate-mount case (lexically-outside\n // path that resolves back inside the workspace). The lexical\n // check alone gives the wrong answer for either, so we don't\n // bother computing it.\n const realInside = await isInsideAnyRootRealpath(abs, realRoots);\n if (!realInside) {\n outside.push(p);\n }\n }\n if (outside.length === 0) return { decision: 'allow' };\n\n const policy = resolvePolicy(input.toolName);\n if (policy === 'allow') return { decision: 'allow' };\n\n const decision: ToolDecision = policy === 'deny' ? 'deny' : 'ask';\n return {\n decision,\n reason: formatReason(config.reason, input.toolName, outside),\n ...(decision === 'ask'\n ? { allowedDecisions: ['approve', 'reject'] as const }\n : {}),\n };\n };\n}\n"],"mappings":"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"}
|
|
@@ -47,12 +47,20 @@ var CustomChatBedrockConverse = class extends _langchain_aws.ChatBedrockConverse
|
|
|
47
47
|
* Service tier for model invocation.
|
|
48
48
|
*/
|
|
49
49
|
serviceTier;
|
|
50
|
+
/**
|
|
51
|
+
* The configured model id, captured at construction so it survives the
|
|
52
|
+
* temporary `this.model` swap to an application-inference-profile ARN during
|
|
53
|
+
* generation. Used to gate the Bedrock tool cache point to Claude models
|
|
54
|
+
* (see {@link supportsBedrockToolCache}).
|
|
55
|
+
*/
|
|
56
|
+
cacheModelId;
|
|
50
57
|
constructor(fields) {
|
|
51
58
|
super(fields);
|
|
52
59
|
this.promptCache = fields?.promptCache;
|
|
53
60
|
this.promptCacheTtl = fields?.promptCacheTtl;
|
|
54
61
|
this.applicationInferenceProfile = fields?.applicationInferenceProfile;
|
|
55
62
|
this.serviceTier = fields?.serviceTier;
|
|
63
|
+
this.cacheModelId = fields?.model ?? this.model;
|
|
56
64
|
}
|
|
57
65
|
static lc_name() {
|
|
58
66
|
return "LibreChatBedrockConverse";
|
|
@@ -69,7 +77,7 @@ var CustomChatBedrockConverse = class extends _langchain_aws.ChatBedrockConverse
|
|
|
69
77
|
*/
|
|
70
78
|
invocationParams(options) {
|
|
71
79
|
const baseParams = super.invocationParams(options);
|
|
72
|
-
const toolConfig = this.promptCache === true ? require_toolCache.insertBedrockToolCachePoint(baseParams.toolConfig, true, require_cache.resolvePromptCacheTtl(this.promptCacheTtl)) : baseParams.toolConfig;
|
|
80
|
+
const toolConfig = this.promptCache === true && require_cache.supportsBedrockToolCache(this.cacheModelId) ? require_toolCache.insertBedrockToolCachePoint(baseParams.toolConfig, true, require_cache.resolvePromptCacheTtl(this.promptCacheTtl)) : baseParams.toolConfig;
|
|
73
81
|
/** Service tier from options or fall back to class-level setting */
|
|
74
82
|
const serviceTierType = options?.serviceTier ?? this.serviceTier;
|
|
75
83
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["ChatBedrockConverse","insertBedrockToolCachePoint","resolvePromptCacheTtl","convertToConverseMessages","ConverseStreamCommand","handleConverseStreamContentBlockStart","handleConverseStreamContentBlockDelta","handleConverseStreamMetadata","createConverseToolUseStopChunk","ChatGenerationChunk","AIMessageChunk"],"sources":["../../../../src/llm/bedrock/index.ts"],"sourcesContent":["/**\n * Optimized ChatBedrockConverse wrapper that fixes content block merging for\n * streaming responses and adds support for latest @langchain/aws features:\n *\n * - Application Inference Profiles (PR #9129)\n * - Service Tiers (Priority/Standard/Flex) (PR #9785) - requires AWS SDK 3.966.0+\n *\n * Bedrock's `@langchain/aws` library does not include an `index` property on content\n * blocks (unlike Anthropic/OpenAI), which causes LangChain's `_mergeLists` to append\n * each streaming chunk as a separate array entry instead of merging by index.\n *\n * This wrapper takes full ownership of the stream by directly interfacing with the\n * AWS SDK client (`this.client`) and using custom handlers from `./utils/` that\n * include `contentBlockIndex` in response_metadata for every delta type. It then\n * promotes `contentBlockIndex` to an `index` property on each content block\n * (mirroring Anthropic's pattern) and strips it from metadata to avoid\n * `_mergeDicts` conflicts.\n *\n * When multiple content block types are present (e.g. reasoning + text), text deltas\n * are promoted from strings to array form with `index` so they merge correctly once\n * the accumulated content is already an array.\n */\n\nimport { ChatBedrockConverse } from '@langchain/aws';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport { ChatGenerationChunk, ChatResult } from '@langchain/core/outputs';\nimport {\n ConverseStreamCommand,\n type GuardrailConfiguration,\n type GuardrailStreamConfiguration,\n} from '@aws-sdk/client-bedrock-runtime';\nimport type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';\nimport type { BaseMessage, ResponseMetadata } from '@langchain/core/messages';\nimport type { ChatBedrockConverseInput } from '@langchain/aws';\nimport {\n convertToConverseMessages,\n createConverseToolUseStopChunk,\n handleConverseStreamContentBlockStart,\n handleConverseStreamContentBlockDelta,\n handleConverseStreamMetadata,\n} from './utils';\nimport { resolvePromptCacheTtl, type PromptCacheTtl } from '@/messages/cache';\nimport { insertBedrockToolCachePoint } from './toolCache';\n\n/**\n * Service tier type for Bedrock invocations.\n * Requires AWS SDK >= 3.966.0 to actually work.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html\n */\nexport type ServiceTierType = 'priority' | 'default' | 'flex' | 'reserved';\n\nexport type CustomGuardrailConfiguration = GuardrailConfiguration &\n Pick<GuardrailStreamConfiguration, 'streamProcessingMode'>;\n\n/**\n * Extended input interface with additional features:\n * - applicationInferenceProfile: Use an inference profile ARN instead of model ID\n * - serviceTier: Specify service tier (Priority, Standard, Flex, Reserved)\n */\nexport interface CustomChatBedrockConverseInput\n extends ChatBedrockConverseInput {\n /**\n * Enables Bedrock prompt cache checkpoints for message and tool prefixes.\n */\n promptCache?: boolean;\n\n /**\n * Prompt-cache checkpoint TTL. Defaults to `'1h'` (extended cache) when\n * `promptCache` is enabled; set `'5m'` for the legacy 5-minute behavior.\n * Bedrock models that don't support the 1-hour TTL downgrade to 5m\n * server-side (verified on Sonnet/Opus 4.6), so the default is safe to leave\n * on; use `'5m'` for any model that rejects it.\n */\n promptCacheTtl?: PromptCacheTtl;\n\n /**\n * Guardrail configuration for Converse and ConverseStream invocations.\n * `streamProcessingMode` is only used by ConverseStream.\n */\n guardrailConfig?: CustomGuardrailConfiguration;\n\n /**\n * Application Inference Profile ARN to use for the model.\n * For example, \"arn:aws:bedrock:eu-west-1:123456789102:application-inference-profile/fm16bt65tzgx\"\n * When provided, this ARN will be used for the actual inference calls instead of the model ID.\n * Must still provide `model` as normal modelId to benefit from all the metadata.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-create.html\n */\n applicationInferenceProfile?: string;\n\n /**\n * Service tier for model invocation.\n * Specifies the processing tier type used for serving the request.\n * Supported values are 'priority', 'default', 'flex', and 'reserved'.\n *\n * - 'priority': Prioritized processing for lower latency\n * - 'default': Standard processing tier\n * - 'flex': Flexible processing tier with lower cost\n * - 'reserved': Reserved capacity for consistent performance\n *\n * If not provided, AWS uses the default tier.\n * Note: Requires AWS SDK >= 3.966.0 to work.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html\n */\n serviceTier?: ServiceTierType;\n}\n\n/**\n * Extended call options with serviceTier override support.\n */\nexport interface CustomChatBedrockConverseCallOptions {\n serviceTier?: ServiceTierType;\n guardrailConfig?: CustomGuardrailConfiguration;\n}\n\nexport class CustomChatBedrockConverse extends ChatBedrockConverse {\n /**\n * Whether to insert Bedrock prompt cache checkpoints when available.\n */\n promptCache?: boolean;\n\n /**\n * Prompt-cache checkpoint TTL (`'5m'` legacy or `'1h'` extended cache).\n */\n promptCacheTtl?: PromptCacheTtl;\n\n /**\n * Application Inference Profile ARN to use instead of model ID.\n */\n applicationInferenceProfile?: string;\n\n /**\n * Service tier for model invocation.\n */\n serviceTier?: ServiceTierType;\n\n constructor(fields?: CustomChatBedrockConverseInput) {\n super(fields);\n this.promptCache = fields?.promptCache;\n this.promptCacheTtl = fields?.promptCacheTtl;\n this.applicationInferenceProfile = fields?.applicationInferenceProfile;\n this.serviceTier = fields?.serviceTier;\n }\n\n static lc_name(): string {\n return 'LibreChatBedrockConverse';\n }\n\n /**\n * Get the model ID to use for API calls.\n * Returns applicationInferenceProfile if set, otherwise returns this.model.\n */\n protected getModelId(): string {\n return this.applicationInferenceProfile ?? this.model;\n }\n\n /**\n * Override invocationParams to add serviceTier support.\n */\n override invocationParams(\n options?: this['ParsedCallOptions'] & CustomChatBedrockConverseCallOptions\n ): ReturnType<ChatBedrockConverse['invocationParams']> & {\n serviceTier?: { type: ServiceTierType };\n } {\n const baseParams = super.invocationParams(options);\n const toolConfig =\n this.promptCache === true\n ? insertBedrockToolCachePoint(\n baseParams.toolConfig,\n true,\n resolvePromptCacheTtl(this.promptCacheTtl)\n )\n : baseParams.toolConfig;\n\n /** Service tier from options or fall back to class-level setting */\n const serviceTierType = options?.serviceTier ?? this.serviceTier;\n\n return {\n ...baseParams,\n toolConfig,\n serviceTier: serviceTierType ? { type: serviceTierType } : undefined,\n };\n }\n\n /**\n * Override _generateNonStreaming to use applicationInferenceProfile as modelId.\n * Uses the same model-swapping pattern as streaming for consistency.\n */\n override async _generateNonStreaming(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'] & CustomChatBedrockConverseCallOptions,\n runManager?: CallbackManagerForLLMRun\n ): Promise<ChatResult> {\n const originalModel = this.model;\n if (\n this.applicationInferenceProfile != null &&\n this.applicationInferenceProfile !== ''\n ) {\n this.model = this.applicationInferenceProfile;\n }\n\n try {\n return await super._generateNonStreaming(messages, options, runManager);\n } finally {\n this.model = originalModel;\n }\n }\n\n /**\n * Own the stream end-to-end so we have direct access to every\n * `contentBlockDelta.contentBlockIndex` from the AWS SDK.\n *\n * This replaces the parent's implementation which strips contentBlockIndex\n * from text and reasoning deltas, making it impossible to merge correctly.\n */\n override async *_streamResponseChunks(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'] & CustomChatBedrockConverseCallOptions,\n runManager?: CallbackManagerForLLMRun\n ): AsyncGenerator<ChatGenerationChunk> {\n const { converseMessages, converseSystem } =\n convertToConverseMessages(messages);\n const params = this.invocationParams(options);\n\n let { streamUsage } = this;\n if ((options as Record<string, unknown>).streamUsage !== undefined) {\n streamUsage = (options as Record<string, unknown>).streamUsage as boolean;\n }\n\n const modelId = this.getModelId();\n\n const command = new ConverseStreamCommand({\n modelId,\n messages: converseMessages,\n system: converseSystem,\n ...(params as Record<string, unknown>),\n });\n\n const response = await this.client.send(command, {\n abortSignal: options.signal,\n });\n\n if (!response.stream) {\n return;\n }\n\n const seenBlockIndices = new Set<number>();\n const toolUseBlockIndices = new Set<number>();\n /**\n * Guardrails can reject an already-streamed toolUse block at\n * `messageStop` (`guardrail_intervened`), after `contentBlockStop` has\n * passed. Only emit eager-execution seals when no guardrails are\n * configured, so a later intervention can't race an eagerly started tool.\n */\n const sealToolUseOnStop =\n options.guardrailConfig == null && this.guardrailConfig == null;\n\n for await (const event of response.stream) {\n if (event.contentBlockStart != null) {\n const startChunk = handleConverseStreamContentBlockStart(\n event.contentBlockStart\n );\n if (startChunk != null) {\n const idx = event.contentBlockStart.contentBlockIndex;\n if (idx != null) {\n seenBlockIndices.add(idx);\n if (event.contentBlockStart.start?.toolUse != null) {\n toolUseBlockIndices.add(idx);\n }\n }\n yield this.enrichChunk(startChunk, seenBlockIndices);\n\n // Registered stream handlers receive chunks through callback\n // events, not the yielded generator — dispatch the start chunk so\n // they see the tool call's id/name (eager chunk state needs both).\n await runManager?.handleLLMNewToken(\n startChunk.text,\n undefined,\n undefined,\n undefined,\n undefined,\n { chunk: startChunk }\n );\n }\n } else if (event.contentBlockDelta != null) {\n const deltaChunk = handleConverseStreamContentBlockDelta(\n event.contentBlockDelta\n );\n\n const idx = event.contentBlockDelta.contentBlockIndex;\n if (idx != null) {\n seenBlockIndices.add(idx);\n }\n\n yield this.enrichChunk(deltaChunk, seenBlockIndices);\n\n await runManager?.handleLLMNewToken(\n deltaChunk.text,\n undefined,\n undefined,\n undefined,\n undefined,\n { chunk: deltaChunk }\n );\n } else if (event.metadata != null) {\n yield handleConverseStreamMetadata(event.metadata, { streamUsage });\n } else if (event.contentBlockStop != null) {\n const stopIdx = event.contentBlockStop.contentBlockIndex;\n if (stopIdx != null) {\n seenBlockIndices.add(stopIdx);\n if (sealToolUseOnStop && toolUseBlockIndices.has(stopIdx)) {\n // Converse guarantees the block's input is complete at stop, so\n // emit an explicit seal chunk for eager tool execution — through\n // the callback path too, for registered stream handlers.\n const sealChunk = createConverseToolUseStopChunk(stopIdx);\n yield sealChunk;\n await runManager?.handleLLMNewToken(\n sealChunk.text,\n undefined,\n undefined,\n undefined,\n undefined,\n { chunk: sealChunk }\n );\n }\n }\n } else {\n yield new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n response_metadata: { ...event } as ResponseMetadata,\n }),\n });\n }\n }\n }\n\n /**\n * Inject `index` on content blocks for proper merge behaviour, then strip\n * `contentBlockIndex` from response_metadata to prevent `_mergeDicts` conflicts.\n *\n * Text string content is promoted to array form only when the stream contains\n * multiple content block indices (e.g. reasoning at index 0, text at index 1),\n * ensuring text merges correctly with the already-array accumulated content.\n */\n private enrichChunk(\n chunk: ChatGenerationChunk,\n seenBlockIndices: Set<number>\n ): ChatGenerationChunk {\n const message = chunk.message;\n if (!(message instanceof AIMessageChunk)) {\n return chunk;\n }\n\n const metadata = message.response_metadata as Record<string, unknown>;\n const blockIndex = this.extractContentBlockIndex(metadata);\n const hasMetadataIndex = blockIndex != null;\n\n let content: AIMessageChunk['content'] = message.content;\n let contentModified = false;\n\n if (Array.isArray(content) && blockIndex != null) {\n content = content.map((block) =>\n typeof block === 'object' && !('index' in block)\n ? { ...block, index: blockIndex }\n : block\n );\n contentModified = true;\n } else if (\n typeof content === 'string' &&\n content !== '' &&\n blockIndex != null &&\n seenBlockIndices.size > 1\n ) {\n content = [{ type: 'text', text: content, index: blockIndex }];\n contentModified = true;\n }\n\n if (!contentModified && !hasMetadataIndex) {\n return chunk;\n }\n\n const cleanedMetadata = hasMetadataIndex\n ? (this.removeContentBlockIndex(metadata) as Record<string, unknown>)\n : metadata;\n\n return new ChatGenerationChunk({\n text: chunk.text,\n message: new AIMessageChunk({\n ...message,\n content,\n response_metadata: cleanedMetadata,\n }),\n generationInfo: chunk.generationInfo,\n });\n }\n\n /**\n * Extract `contentBlockIndex` from the top level of response_metadata.\n * Our custom handlers always place it at the top level.\n */\n private extractContentBlockIndex(\n metadata: Record<string, unknown>\n ): number | undefined {\n if (\n 'contentBlockIndex' in metadata &&\n typeof metadata.contentBlockIndex === 'number'\n ) {\n return metadata.contentBlockIndex;\n }\n return undefined;\n }\n\n private removeContentBlockIndex(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => this.removeContentBlockIndex(item));\n }\n\n if (typeof obj === 'object') {\n const cleaned: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (key !== 'contentBlockIndex') {\n cleaned[key] = this.removeContentBlockIndex(value);\n }\n }\n return cleaned;\n }\n\n return obj;\n }\n}\n\nexport type { ChatBedrockConverseInput };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["ChatBedrockConverse","supportsBedrockToolCache","insertBedrockToolCachePoint","resolvePromptCacheTtl","convertToConverseMessages","ConverseStreamCommand","handleConverseStreamContentBlockStart","handleConverseStreamContentBlockDelta","handleConverseStreamMetadata","createConverseToolUseStopChunk","ChatGenerationChunk","AIMessageChunk"],"sources":["../../../../src/llm/bedrock/index.ts"],"sourcesContent":["/**\n * Optimized ChatBedrockConverse wrapper that fixes content block merging for\n * streaming responses and adds support for latest @langchain/aws features:\n *\n * - Application Inference Profiles (PR #9129)\n * - Service Tiers (Priority/Standard/Flex) (PR #9785) - requires AWS SDK 3.966.0+\n *\n * Bedrock's `@langchain/aws` library does not include an `index` property on content\n * blocks (unlike Anthropic/OpenAI), which causes LangChain's `_mergeLists` to append\n * each streaming chunk as a separate array entry instead of merging by index.\n *\n * This wrapper takes full ownership of the stream by directly interfacing with the\n * AWS SDK client (`this.client`) and using custom handlers from `./utils/` that\n * include `contentBlockIndex` in response_metadata for every delta type. It then\n * promotes `contentBlockIndex` to an `index` property on each content block\n * (mirroring Anthropic's pattern) and strips it from metadata to avoid\n * `_mergeDicts` conflicts.\n *\n * When multiple content block types are present (e.g. reasoning + text), text deltas\n * are promoted from strings to array form with `index` so they merge correctly once\n * the accumulated content is already an array.\n */\n\nimport { ChatBedrockConverse } from '@langchain/aws';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport { ChatGenerationChunk, ChatResult } from '@langchain/core/outputs';\nimport {\n ConverseStreamCommand,\n type GuardrailConfiguration,\n type GuardrailStreamConfiguration,\n} from '@aws-sdk/client-bedrock-runtime';\nimport type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';\nimport type { BaseMessage, ResponseMetadata } from '@langchain/core/messages';\nimport type { ChatBedrockConverseInput } from '@langchain/aws';\nimport {\n convertToConverseMessages,\n createConverseToolUseStopChunk,\n handleConverseStreamContentBlockStart,\n handleConverseStreamContentBlockDelta,\n handleConverseStreamMetadata,\n} from './utils';\nimport {\n resolvePromptCacheTtl,\n supportsBedrockToolCache,\n type PromptCacheTtl,\n} from '@/messages/cache';\nimport { insertBedrockToolCachePoint } from './toolCache';\n\n/**\n * Service tier type for Bedrock invocations.\n * Requires AWS SDK >= 3.966.0 to actually work.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html\n */\nexport type ServiceTierType = 'priority' | 'default' | 'flex' | 'reserved';\n\nexport type CustomGuardrailConfiguration = GuardrailConfiguration &\n Pick<GuardrailStreamConfiguration, 'streamProcessingMode'>;\n\n/**\n * Extended input interface with additional features:\n * - applicationInferenceProfile: Use an inference profile ARN instead of model ID\n * - serviceTier: Specify service tier (Priority, Standard, Flex, Reserved)\n */\nexport interface CustomChatBedrockConverseInput\n extends ChatBedrockConverseInput {\n /**\n * Enables Bedrock prompt cache checkpoints for message and tool prefixes.\n */\n promptCache?: boolean;\n\n /**\n * Prompt-cache checkpoint TTL. Defaults to `'1h'` (extended cache) when\n * `promptCache` is enabled; set `'5m'` for the legacy 5-minute behavior.\n * Bedrock models that don't support the 1-hour TTL downgrade to 5m\n * server-side (verified on Sonnet/Opus 4.6), so the default is safe to leave\n * on; use `'5m'` for any model that rejects it.\n */\n promptCacheTtl?: PromptCacheTtl;\n\n /**\n * Guardrail configuration for Converse and ConverseStream invocations.\n * `streamProcessingMode` is only used by ConverseStream.\n */\n guardrailConfig?: CustomGuardrailConfiguration;\n\n /**\n * Application Inference Profile ARN to use for the model.\n * For example, \"arn:aws:bedrock:eu-west-1:123456789102:application-inference-profile/fm16bt65tzgx\"\n * When provided, this ARN will be used for the actual inference calls instead of the model ID.\n * Must still provide `model` as normal modelId to benefit from all the metadata.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-create.html\n */\n applicationInferenceProfile?: string;\n\n /**\n * Service tier for model invocation.\n * Specifies the processing tier type used for serving the request.\n * Supported values are 'priority', 'default', 'flex', and 'reserved'.\n *\n * - 'priority': Prioritized processing for lower latency\n * - 'default': Standard processing tier\n * - 'flex': Flexible processing tier with lower cost\n * - 'reserved': Reserved capacity for consistent performance\n *\n * If not provided, AWS uses the default tier.\n * Note: Requires AWS SDK >= 3.966.0 to work.\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html\n */\n serviceTier?: ServiceTierType;\n}\n\n/**\n * Extended call options with serviceTier override support.\n */\nexport interface CustomChatBedrockConverseCallOptions {\n serviceTier?: ServiceTierType;\n guardrailConfig?: CustomGuardrailConfiguration;\n}\n\nexport class CustomChatBedrockConverse extends ChatBedrockConverse {\n /**\n * Whether to insert Bedrock prompt cache checkpoints when available.\n */\n promptCache?: boolean;\n\n /**\n * Prompt-cache checkpoint TTL (`'5m'` legacy or `'1h'` extended cache).\n */\n promptCacheTtl?: PromptCacheTtl;\n\n /**\n * Application Inference Profile ARN to use instead of model ID.\n */\n applicationInferenceProfile?: string;\n\n /**\n * Service tier for model invocation.\n */\n serviceTier?: ServiceTierType;\n\n /**\n * The configured model id, captured at construction so it survives the\n * temporary `this.model` swap to an application-inference-profile ARN during\n * generation. Used to gate the Bedrock tool cache point to Claude models\n * (see {@link supportsBedrockToolCache}).\n */\n private readonly cacheModelId: string;\n\n constructor(fields?: CustomChatBedrockConverseInput) {\n super(fields);\n this.promptCache = fields?.promptCache;\n this.promptCacheTtl = fields?.promptCacheTtl;\n this.applicationInferenceProfile = fields?.applicationInferenceProfile;\n this.serviceTier = fields?.serviceTier;\n // `super(fields)` initializes `this.model` to LangChain's default Claude\n // model when `fields.model` is omitted, so fall back to it rather than ''\n // (which would treat the default Claude model as tool-cache-unsupported).\n this.cacheModelId = fields?.model ?? this.model;\n }\n\n static lc_name(): string {\n return 'LibreChatBedrockConverse';\n }\n\n /**\n * Get the model ID to use for API calls.\n * Returns applicationInferenceProfile if set, otherwise returns this.model.\n */\n protected getModelId(): string {\n return this.applicationInferenceProfile ?? this.model;\n }\n\n /**\n * Override invocationParams to add serviceTier support.\n */\n override invocationParams(\n options?: this['ParsedCallOptions'] & CustomChatBedrockConverseCallOptions\n ): ReturnType<ChatBedrockConverse['invocationParams']> & {\n serviceTier?: { type: ServiceTierType };\n } {\n const baseParams = super.invocationParams(options);\n const toolConfig =\n this.promptCache === true && supportsBedrockToolCache(this.cacheModelId)\n ? insertBedrockToolCachePoint(\n baseParams.toolConfig,\n true,\n resolvePromptCacheTtl(this.promptCacheTtl)\n )\n : baseParams.toolConfig;\n\n /** Service tier from options or fall back to class-level setting */\n const serviceTierType = options?.serviceTier ?? this.serviceTier;\n\n return {\n ...baseParams,\n toolConfig,\n serviceTier: serviceTierType ? { type: serviceTierType } : undefined,\n };\n }\n\n /**\n * Override _generateNonStreaming to use applicationInferenceProfile as modelId.\n * Uses the same model-swapping pattern as streaming for consistency.\n */\n override async _generateNonStreaming(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'] & CustomChatBedrockConverseCallOptions,\n runManager?: CallbackManagerForLLMRun\n ): Promise<ChatResult> {\n const originalModel = this.model;\n if (\n this.applicationInferenceProfile != null &&\n this.applicationInferenceProfile !== ''\n ) {\n this.model = this.applicationInferenceProfile;\n }\n\n try {\n return await super._generateNonStreaming(messages, options, runManager);\n } finally {\n this.model = originalModel;\n }\n }\n\n /**\n * Own the stream end-to-end so we have direct access to every\n * `contentBlockDelta.contentBlockIndex` from the AWS SDK.\n *\n * This replaces the parent's implementation which strips contentBlockIndex\n * from text and reasoning deltas, making it impossible to merge correctly.\n */\n override async *_streamResponseChunks(\n messages: BaseMessage[],\n options: this['ParsedCallOptions'] & CustomChatBedrockConverseCallOptions,\n runManager?: CallbackManagerForLLMRun\n ): AsyncGenerator<ChatGenerationChunk> {\n const { converseMessages, converseSystem } =\n convertToConverseMessages(messages);\n const params = this.invocationParams(options);\n\n let { streamUsage } = this;\n if ((options as Record<string, unknown>).streamUsage !== undefined) {\n streamUsage = (options as Record<string, unknown>).streamUsage as boolean;\n }\n\n const modelId = this.getModelId();\n\n const command = new ConverseStreamCommand({\n modelId,\n messages: converseMessages,\n system: converseSystem,\n ...(params as Record<string, unknown>),\n });\n\n const response = await this.client.send(command, {\n abortSignal: options.signal,\n });\n\n if (!response.stream) {\n return;\n }\n\n const seenBlockIndices = new Set<number>();\n const toolUseBlockIndices = new Set<number>();\n /**\n * Guardrails can reject an already-streamed toolUse block at\n * `messageStop` (`guardrail_intervened`), after `contentBlockStop` has\n * passed. Only emit eager-execution seals when no guardrails are\n * configured, so a later intervention can't race an eagerly started tool.\n */\n const sealToolUseOnStop =\n options.guardrailConfig == null && this.guardrailConfig == null;\n\n for await (const event of response.stream) {\n if (event.contentBlockStart != null) {\n const startChunk = handleConverseStreamContentBlockStart(\n event.contentBlockStart\n );\n if (startChunk != null) {\n const idx = event.contentBlockStart.contentBlockIndex;\n if (idx != null) {\n seenBlockIndices.add(idx);\n if (event.contentBlockStart.start?.toolUse != null) {\n toolUseBlockIndices.add(idx);\n }\n }\n yield this.enrichChunk(startChunk, seenBlockIndices);\n\n // Registered stream handlers receive chunks through callback\n // events, not the yielded generator — dispatch the start chunk so\n // they see the tool call's id/name (eager chunk state needs both).\n await runManager?.handleLLMNewToken(\n startChunk.text,\n undefined,\n undefined,\n undefined,\n undefined,\n { chunk: startChunk }\n );\n }\n } else if (event.contentBlockDelta != null) {\n const deltaChunk = handleConverseStreamContentBlockDelta(\n event.contentBlockDelta\n );\n\n const idx = event.contentBlockDelta.contentBlockIndex;\n if (idx != null) {\n seenBlockIndices.add(idx);\n }\n\n yield this.enrichChunk(deltaChunk, seenBlockIndices);\n\n await runManager?.handleLLMNewToken(\n deltaChunk.text,\n undefined,\n undefined,\n undefined,\n undefined,\n { chunk: deltaChunk }\n );\n } else if (event.metadata != null) {\n yield handleConverseStreamMetadata(event.metadata, { streamUsage });\n } else if (event.contentBlockStop != null) {\n const stopIdx = event.contentBlockStop.contentBlockIndex;\n if (stopIdx != null) {\n seenBlockIndices.add(stopIdx);\n if (sealToolUseOnStop && toolUseBlockIndices.has(stopIdx)) {\n // Converse guarantees the block's input is complete at stop, so\n // emit an explicit seal chunk for eager tool execution — through\n // the callback path too, for registered stream handlers.\n const sealChunk = createConverseToolUseStopChunk(stopIdx);\n yield sealChunk;\n await runManager?.handleLLMNewToken(\n sealChunk.text,\n undefined,\n undefined,\n undefined,\n undefined,\n { chunk: sealChunk }\n );\n }\n }\n } else {\n yield new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n response_metadata: { ...event } as ResponseMetadata,\n }),\n });\n }\n }\n }\n\n /**\n * Inject `index` on content blocks for proper merge behaviour, then strip\n * `contentBlockIndex` from response_metadata to prevent `_mergeDicts` conflicts.\n *\n * Text string content is promoted to array form only when the stream contains\n * multiple content block indices (e.g. reasoning at index 0, text at index 1),\n * ensuring text merges correctly with the already-array accumulated content.\n */\n private enrichChunk(\n chunk: ChatGenerationChunk,\n seenBlockIndices: Set<number>\n ): ChatGenerationChunk {\n const message = chunk.message;\n if (!(message instanceof AIMessageChunk)) {\n return chunk;\n }\n\n const metadata = message.response_metadata as Record<string, unknown>;\n const blockIndex = this.extractContentBlockIndex(metadata);\n const hasMetadataIndex = blockIndex != null;\n\n let content: AIMessageChunk['content'] = message.content;\n let contentModified = false;\n\n if (Array.isArray(content) && blockIndex != null) {\n content = content.map((block) =>\n typeof block === 'object' && !('index' in block)\n ? { ...block, index: blockIndex }\n : block\n );\n contentModified = true;\n } else if (\n typeof content === 'string' &&\n content !== '' &&\n blockIndex != null &&\n seenBlockIndices.size > 1\n ) {\n content = [{ type: 'text', text: content, index: blockIndex }];\n contentModified = true;\n }\n\n if (!contentModified && !hasMetadataIndex) {\n return chunk;\n }\n\n const cleanedMetadata = hasMetadataIndex\n ? (this.removeContentBlockIndex(metadata) as Record<string, unknown>)\n : metadata;\n\n return new ChatGenerationChunk({\n text: chunk.text,\n message: new AIMessageChunk({\n ...message,\n content,\n response_metadata: cleanedMetadata,\n }),\n generationInfo: chunk.generationInfo,\n });\n }\n\n /**\n * Extract `contentBlockIndex` from the top level of response_metadata.\n * Our custom handlers always place it at the top level.\n */\n private extractContentBlockIndex(\n metadata: Record<string, unknown>\n ): number | undefined {\n if (\n 'contentBlockIndex' in metadata &&\n typeof metadata.contentBlockIndex === 'number'\n ) {\n return metadata.contentBlockIndex;\n }\n return undefined;\n }\n\n private removeContentBlockIndex(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => this.removeContentBlockIndex(item));\n }\n\n if (typeof obj === 'object') {\n const cleaned: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (key !== 'contentBlockIndex') {\n cleaned[key] = this.removeContentBlockIndex(value);\n }\n }\n return cleaned;\n }\n\n return obj;\n }\n}\n\nexport type { ChatBedrockConverseInput };\n"],"mappings":"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"}
|
package/dist/cjs/main.cjs
CHANGED
|
@@ -360,6 +360,7 @@ exports.calculateMaxTotalToolOutputSize = require_truncation.calculateMaxTotalTo
|
|
|
360
360
|
exports.calculateTotalTokens = require_prune.calculateTotalTokens;
|
|
361
361
|
exports.checkValidNumber = require_prune.checkValidNumber;
|
|
362
362
|
exports.classifyAttachment = require_attachments.classifyAttachment;
|
|
363
|
+
exports.clientExecTimeoutMs = require_CloudflareSandboxExecutionEngine.clientExecTimeoutMs;
|
|
363
364
|
exports.cloneMessage = require_cache.cloneMessage;
|
|
364
365
|
exports.composeEventHandlers = require_events.composeEventHandlers;
|
|
365
366
|
exports.convertMessagesToContent = require_core.convertMessagesToContent;
|
|
@@ -426,6 +427,7 @@ exports.ensureThinkingBlockInMessages = require_format.ensureThinkingBlockInMess
|
|
|
426
427
|
exports.escapeRegexSpecialChars = require_ToolSearch.escapeRegexSpecialChars;
|
|
427
428
|
exports.estimateAnthropicImageTokens = require_tokens.estimateAnthropicImageTokens;
|
|
428
429
|
exports.estimateOpenAIImageTokens = require_tokens.estimateOpenAIImageTokens;
|
|
430
|
+
exports.execWithClientTimeout = require_CloudflareSandboxExecutionEngine.execWithClientTimeout;
|
|
429
431
|
exports.executeCloudflareBash = require_CloudflareSandboxExecutionEngine.executeCloudflareBash;
|
|
430
432
|
exports.executeCloudflareCode = require_CloudflareSandboxExecutionEngine.executeCloudflareCode;
|
|
431
433
|
exports.executeHooks = require_executeHooks.executeHooks;
|
|
@@ -583,6 +585,7 @@ exports.stripAnthropicCacheControl = require_cache.stripAnthropicCacheControl;
|
|
|
583
585
|
exports.stripBedrockCacheControl = require_cache.stripBedrockCacheControl;
|
|
584
586
|
exports.stripCodeSessionFileSummary = require_CodeSessionFileSummary.stripCodeSessionFileSummary;
|
|
585
587
|
exports.summarizeEvent = require_SubagentExecutor.summarizeEvent;
|
|
588
|
+
exports.supportsBedrockToolCache = require_cache.supportsBedrockToolCache;
|
|
586
589
|
exports.syncBudgetDerivedFields = require_budget.syncBudgetDerivedFields;
|
|
587
590
|
exports.toJsonSchema = require_schema.toJsonSchema;
|
|
588
591
|
Object.defineProperty(exports, "tool", {
|
|
@@ -602,4 +605,5 @@ exports.unwrapToolResponse = require_ProgrammaticToolCalling.unwrapToolResponse;
|
|
|
602
605
|
exports.validateBashCommand = require_LocalExecutionEngine.validateBashCommand;
|
|
603
606
|
exports.validateCloudflareBashCommand = require_CloudflareSandboxExecutionEngine.validateCloudflareBashCommand;
|
|
604
607
|
exports.videosSchema = require_schema$2.videosSchema;
|
|
608
|
+
exports.withClientTimeout = require_CloudflareSandboxExecutionEngine.withClientTimeout;
|
|
605
609
|
exports.withMessageRole = require_format.withMessageRole;
|
|
@@ -40,6 +40,26 @@ function buildBedrockCachePoint(ttl) {
|
|
|
40
40
|
} : { type: "default" };
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
+
* A `cachePoint` under `toolConfig.tools` is only accepted on Anthropic Claude
|
|
44
|
+
* models. Amazon Nova rejects it outright — `Malformed input request:
|
|
45
|
+
* #/toolConfig/tools/0: extraneous key [cachePoint] is not permitted` — even
|
|
46
|
+
* though it accepts `cachePoint` in `system` and `messages` (verified live:
|
|
47
|
+
* Nova returns HTTP 200 with real `cacheWriteInputTokens` for both). Per the AWS
|
|
48
|
+
* prompt-caching docs the support table lists `tools` for Claude only.
|
|
49
|
+
*
|
|
50
|
+
* Gate ONLY the tool checkpoint on this, so a `promptCache: true` config on Nova
|
|
51
|
+
* keeps its valid message/system caching instead of either 400-ing (tool point)
|
|
52
|
+
* or losing caching entirely. This is a capability gate (the key is rejected
|
|
53
|
+
* outright), distinct from the TTL value which Bedrock downgrades gracefully.
|
|
54
|
+
*
|
|
55
|
+
* @see https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html
|
|
56
|
+
* @see https://github.com/danny-avila/LibreChat/issues/13838
|
|
57
|
+
*/
|
|
58
|
+
function supportsBedrockToolCache(model) {
|
|
59
|
+
if (typeof model !== "string") return false;
|
|
60
|
+
return /claude|anthropic/i.test(model);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
43
63
|
* Deep clones a message's content to prevent mutation of the original.
|
|
44
64
|
*/
|
|
45
65
|
function deepCloneContent(content) {
|
|
@@ -592,5 +612,6 @@ exports.cloneMessage = cloneMessage;
|
|
|
592
612
|
exports.resolvePromptCacheTtl = resolvePromptCacheTtl;
|
|
593
613
|
exports.stripAnthropicCacheControl = stripAnthropicCacheControl;
|
|
594
614
|
exports.stripBedrockCacheControl = stripBedrockCacheControl;
|
|
615
|
+
exports.supportsBedrockToolCache = supportsBedrockToolCache;
|
|
595
616
|
|
|
596
617
|
//# sourceMappingURL=cache.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.cjs","names":["BaseMessage","toLangChainContent","withMessageRole","AIMessage","HumanMessage","SystemMessage","ToolMessage"],"sources":["../../../src/messages/cache.ts"],"sourcesContent":["import {\n AIMessage,\n BaseMessage,\n ToolMessage,\n HumanMessage,\n SystemMessage,\n MessageContentComplex,\n} from '@langchain/core/messages';\nimport type Anthropic from '@anthropic-ai/sdk';\nimport type { AnthropicMessage } from '@/types/messages';\nimport { toLangChainContent } from './langchain';\nimport { ContentTypes } from '@/common/enum';\nimport { withMessageRole } from './format';\n\ntype MessageWithContent = {\n content?: string | MessageContentComplex[];\n};\n\ntype MessageContentWithCacheControl = MessageContentComplex & {\n cache_control?: unknown;\n};\n\n/**\n * Prompt-cache breakpoint TTL.\n *\n * Both Anthropic (`cache_control.ttl`) and Bedrock (`cachePoint.ttl`) accept\n * `'5m'` (the legacy provider default) and `'1h'` (the extended cache). When\n * prompt caching is enabled the SDK now defaults to the 1-hour extended cache\n * (see {@link DEFAULT_PROMPT_CACHE_TTL}); pass `'5m'` to opt back into the\n * legacy 5-minute behavior.\n */\nexport type PromptCacheTtl = '5m' | '1h';\n\n/**\n * Default TTL applied wherever a prompt-cache breakpoint is added. The 1-hour\n * extended cache keeps prefixes warm across longer gaps between turns, at the\n * cost of a higher one-time cache-write multiplier (2x vs 1.25x for 5m).\n */\nexport const DEFAULT_PROMPT_CACHE_TTL: PromptCacheTtl = '1h';\n\n/**\n * Resolve an optionally-configured TTL to a concrete value, defaulting to the\n * 1-hour extended cache. Used at the Anthropic/Bedrock prompt-cache call sites.\n */\nexport function resolvePromptCacheTtl(\n ttl: PromptCacheTtl | undefined\n): PromptCacheTtl {\n return ttl ?? DEFAULT_PROMPT_CACHE_TTL;\n}\n\n/** Anthropic `cache_control` shape (the SDK accepts an optional `ttl`). */\ntype AnthropicCacheControl = { type: 'ephemeral'; ttl?: '1h' };\n\n/**\n * Build an Anthropic `cache_control` breakpoint for the given TTL. `'5m'` (or\n * `undefined`) omits the `ttl` field — that is the provider default, so the\n * payload stays byte-identical to the legacy 5-minute marker. `'1h'` adds the\n * explicit extended-cache `ttl`.\n */\nexport function buildAnthropicCacheControl(\n ttl?: PromptCacheTtl\n): AnthropicCacheControl {\n return ttl === '1h'\n ? { type: 'ephemeral', ttl: '1h' }\n : { type: 'ephemeral' };\n}\n\n/** Bedrock `cachePoint` shape (the SDK accepts an optional `ttl`). */\ntype BedrockCachePoint = { type: 'default'; ttl?: '1h' };\n\n/**\n * Build a Bedrock `cachePoint` for the given TTL. Mirrors\n * {@link buildAnthropicCacheControl}: `'5m'`/`undefined` omits `ttl` (the\n * legacy default), `'1h'` adds the extended-cache `ttl`.\n */\nexport function buildBedrockCachePoint(\n ttl?: PromptCacheTtl\n): BedrockCachePoint {\n return ttl === '1h' ? { type: 'default', ttl: '1h' } : { type: 'default' };\n}\n\n/**\n * Deep clones a message's content to prevent mutation of the original.\n */\nfunction deepCloneContent<T extends string | MessageContentComplex[]>(\n content: T\n): T {\n if (typeof content === 'string') {\n return content;\n }\n if (Array.isArray(content)) {\n return content.map((block) => ({ ...block })) as T;\n }\n return content;\n}\n\n/**\n * Clones a message with new content. For LangChain BaseMessage instances,\n * constructs a proper class instance so that `instanceof` checks are preserved\n * in downstream code (e.g., ensureThinkingBlockInMessages).\n * For plain objects (AnthropicMessage), uses object spread.\n */\nexport function cloneMessage<T extends MessageWithContent>(\n message: T,\n content: string | MessageContentComplex[]\n): T {\n if (message instanceof BaseMessage) {\n const baseParams = {\n content: toLangChainContent(content),\n additional_kwargs: { ...message.additional_kwargs },\n response_metadata: { ...message.response_metadata },\n id: message.id,\n name: message.name,\n };\n\n const msgType = message.getType();\n switch (msgType) {\n case 'ai':\n return withMessageRole(\n new AIMessage({\n ...baseParams,\n tool_calls: (message as unknown as AIMessage).tool_calls,\n }),\n 'assistant'\n ) as unknown as T;\n case 'human':\n return withMessageRole(\n new HumanMessage(baseParams),\n 'user'\n ) as unknown as T;\n case 'system':\n return withMessageRole(\n new SystemMessage(baseParams),\n 'system'\n ) as unknown as T;\n case 'tool':\n return withMessageRole(\n new ToolMessage({\n ...baseParams,\n tool_call_id: (message as unknown as ToolMessage).tool_call_id,\n }),\n 'tool'\n ) as unknown as T;\n default:\n break;\n }\n }\n\n const {\n lc_kwargs: _lc_kwargs,\n lc_serializable: _lc_serializable,\n lc_namespace: _lc_namespace,\n ...rest\n } = message as T & {\n lc_kwargs?: unknown;\n lc_serializable?: unknown;\n lc_namespace?: unknown;\n };\n\n const cloned = { ...rest, content } as T;\n\n // LangChain messages don't have a direct 'role' property - derive it from getType()\n if (\n 'getType' in message &&\n typeof message.getType === 'function' &&\n !('role' in cloned)\n ) {\n const msgType = (message as unknown as BaseMessage).getType();\n const roleMap: Record<string, string> = {\n human: 'user',\n ai: 'assistant',\n system: 'system',\n tool: 'tool',\n };\n (cloned as Record<string, unknown>).role = roleMap[msgType] || msgType;\n }\n\n return cloned;\n}\n\n/**\n * Sanitize a Bedrock system message: strip Anthropic `cache_control` (Bedrock\n * conversion can't use it) and normalize any existing `cachePoint` to the\n * resolved TTL. The normalization matters because Bedrock requires longer-TTL\n * checkpoints to appear before shorter ones — a stale 5-minute system cachePoint\n * (host-supplied or carried over from a 5m config) left ahead of a 1-hour\n * message tail would make the request invalid. System messages are never\n * anchored as the tail breakpoint; this only fixes markers already present.\n */\nfunction sanitizeBedrockSystemMessage<T extends MessageWithContent>(\n message: T,\n ttl?: PromptCacheTtl\n): T {\n const content = message.content;\n if (!Array.isArray(content)) {\n return message;\n }\n\n const sanitized: MessageContentComplex[] = [];\n let modified = false;\n for (const block of content) {\n if (isCachePoint(block)) {\n const existing = (block as { cachePoint?: { ttl?: unknown } }).cachePoint;\n const desired = buildBedrockCachePoint(ttl);\n if (existing?.ttl !== desired.ttl) {\n modified = true;\n sanitized.push({ cachePoint: desired } as MessageContentComplex);\n } else {\n sanitized.push(block);\n }\n continue;\n }\n if ('cache_control' in block) {\n const cloned: MessageContentWithCacheControl = { ...block };\n delete cloned.cache_control;\n modified = true;\n sanitized.push(cloned);\n continue;\n }\n sanitized.push(block);\n }\n\n if (!modified) {\n return message;\n }\n\n return cloneMessage(message, sanitized);\n}\n\n/**\n * Anthropic API: Adds cache control to the appropriate user messages in the payload.\n * Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,\n * then adds fresh cache control to the last 2 user messages in a single backward pass.\n * This ensures we don't accumulate stale cache points across multiple turns.\n * Returns a new array - only clones messages that require modification.\n * @param messages - The array of message objects.\n * @returns - A new array of message objects with cache control added.\n */\nexport function addCacheControl<T extends AnthropicMessage | BaseMessage>(\n messages: T[],\n ttl?: PromptCacheTtl\n): T[] {\n if (!Array.isArray(messages) || messages.length < 2) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let userMessagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n const isUserMessage =\n ('getType' in originalMessage && originalMessage.getType() === 'human') ||\n ('role' in originalMessage && originalMessage.role === 'user');\n const hasArrayContent = Array.isArray(content);\n const needsCacheAdd =\n userMessagesModified < 2 &&\n isUserMessage &&\n !isSyntheticMetaMessage(originalMessage) &&\n (typeof content === 'string' || hasArrayContent);\n\n // Skip messages that don't need any work\n if (!needsCacheAdd && !hasArrayContent) {\n continue;\n }\n\n let workingContent: MessageContentComplex[];\n let modified = false;\n\n if (hasArrayContent) {\n // Single pass: clone blocks, strip cache markers and cache points,\n // find last text block index for cache insertion — all at once.\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastTextIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue; // skip cache point blocks\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n if ('type' in cloned && cloned.type === 'text') {\n lastTextIndex = workingContent.length;\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (!modified && !needsCacheAdd) {\n continue; // nothing to strip and no cache to add\n }\n\n // Add cache control to the last text block for user messages\n if (needsCacheAdd && lastTextIndex >= 0) {\n (\n workingContent[lastTextIndex] as Anthropic.TextBlockParam\n ).cache_control = buildAnthropicCacheControl(ttl);\n userMessagesModified++;\n }\n } else if (typeof content === 'string' && needsCacheAdd) {\n workingContent = [\n {\n type: 'text',\n text: content,\n cache_control: buildAnthropicCacheControl(ttl),\n },\n ] as unknown as MessageContentComplex[];\n userMessagesModified++;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n }\n\n return updatedMessages;\n}\n\n/**\n * Checks if a content block is a cache point\n */\nfunction isCachePoint(block: MessageContentComplex): boolean {\n return 'cachePoint' in block && !('type' in block);\n}\n\n/**\n * Block types that must never anchor the tail cache breakpoint, because the\n * marker would not survive to the model call:\n * - `thinking` / `redacted_thinking`: native Anthropic reasoning — the API\n * rejects `cache_control` on these blocks.\n * - `reasoning_content` / `reasoning` / `think`: foreign reasoning (Bedrock,\n * Google, LibreChat) that `_convertMessagesToAnthropicPayload` DROPS on\n * assistant turns during a cross-provider handoff.\n * - `input_json_delta`: persisted partial tool-input deltas, also DROPPED by\n * `_convertMessagesToAnthropicPayload` (the assembled input is restored onto\n * the tool_use block).\n * Anchoring the only breakpoint on a block that is about to disappear silently\n * loses tail caching, so all of these are excluded.\n */\nconst NON_ANCHORABLE_BLOCK_TYPES = new Set([\n 'thinking',\n 'redacted_thinking',\n 'reasoning_content',\n 'reasoning',\n 'think',\n 'input_json_delta',\n]);\n\n/**\n * A block can anchor the tail cache breakpoint when it is a real content block\n * that the Anthropic API accepts `cache_control` on and that survives provider\n * conversion. Reasoning / dropped-delta blocks are excluded (see\n * {@link NON_ANCHORABLE_BLOCK_TYPES}), and empty text blocks are not cacheable,\n * so both are skipped.\n */\nfunction isTailCacheableBlock(block: MessageContentComplex): boolean {\n if (isCachePoint(block)) {\n return false;\n }\n const type = (block as { type?: string }).type;\n if (type == null || NON_ANCHORABLE_BLOCK_TYPES.has(type)) {\n return false;\n }\n if (type === 'text') {\n const text = (block as { text?: string }).text;\n return text != null && text.trim() !== '';\n }\n return true;\n}\n\n/**\n * Anthropic API: single tail cache breakpoint (default strategy).\n *\n * Places exactly ONE `cache_control` marker on the last cacheable block of the\n * final non-synthetic message, mirroring the Claude Code strategy\n * (`markerIndex = messages.length - 1`). Because the marker always rides the\n * true tail, the entire conversation prefix is written once and read back on\n * the next turn as the history grows append-only — instead of the rolling\n * \"last two user messages\" markers, which leave freshly appended tool/assistant\n * turns outside the cached prefix and re-write large spans every step.\n *\n * Stale markers (Anthropic `cache_control` and Bedrock cache points) are\n * stripped from every message in a single backward pass so exactly one marker\n * survives. Synthetic skill/meta messages are skipped as anchors (their volatile\n * content must not pin the cache) but still have stale markers removed.\n *\n * Returns a new array; only messages that require modification are cloned.\n */\nexport function addTailCacheControl<T extends AnthropicMessage | BaseMessage>(\n messages: T[],\n ttl?: PromptCacheTtl\n): T[] {\n if (!Array.isArray(messages) || messages.length === 0) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let markerPlaced = false;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n const hasArrayContent = Array.isArray(content);\n const canPlaceMarker =\n !markerPlaced && !isSyntheticMetaMessage(originalMessage);\n\n // Earlier string-content messages carry no markers to strip.\n if (!canPlaceMarker && !hasArrayContent) {\n continue;\n }\n\n let workingContent: MessageContentComplex[];\n let modified = false;\n\n if (hasArrayContent) {\n const src = content as MessageContentComplex[];\n workingContent = [];\n let tailIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n if (\n canPlaceMarker &&\n isTailCacheableBlock(cloned as MessageContentComplex)\n ) {\n tailIndex = workingContent.length;\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (canPlaceMarker && tailIndex >= 0) {\n (workingContent[tailIndex] as Anthropic.TextBlockParam).cache_control =\n buildAnthropicCacheControl(ttl);\n markerPlaced = true;\n modified = true;\n }\n\n if (!modified) {\n continue;\n }\n } else if (\n typeof content === 'string' &&\n canPlaceMarker &&\n content.trim() !== ''\n ) {\n workingContent = [\n {\n type: 'text',\n text: content,\n cache_control: buildAnthropicCacheControl(ttl),\n },\n ] as unknown as MessageContentComplex[];\n markerPlaced = true;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n }\n\n return updatedMessages;\n}\n\nfunction getMessageRole(message: MessageWithContent): string | undefined {\n if (message instanceof BaseMessage) {\n return message.getType();\n }\n if ('role' in message && typeof message.role === 'string') {\n return message.role;\n }\n return undefined;\n}\n\nconst SKILL_MESSAGE_SOURCE = 'skill';\n\n/**\n * Synthetic skill/meta messages (reconstructed skill bodies, primed SKILL.md\n * instructions) are re-injected every turn and are not stable conversation\n * turns. They must not anchor a fresh prompt-cache marker — doing so pins the\n * cache to a volatile/duplicated prefix. Stale markers are still stripped from\n * them; only the *adding* of new markers is suppressed. Detected via\n * `additional_kwargs.isMeta === true` or `additional_kwargs.source === 'skill'`.\n */\nfunction isSyntheticMetaMessage(message: MessageWithContent): boolean {\n const { additional_kwargs: kwargs } = message as {\n additional_kwargs?: { isMeta?: unknown; source?: unknown };\n };\n if (kwargs == null) {\n return false;\n }\n return kwargs.isMeta === true || kwargs.source === SKILL_MESSAGE_SOURCE;\n}\n\nfunction isCacheableConversationMessage(message: MessageWithContent): boolean {\n const role = getMessageRole(message);\n return (\n role === 'human' || role === 'user' || role === 'ai' || role === 'assistant'\n );\n}\n\nfunction isAssistantConversationMessage(message: MessageWithContent): boolean {\n const role = getMessageRole(message);\n return role === 'ai' || role === 'assistant';\n}\n\nfunction hasCacheMarker(message: MessageWithContent): boolean {\n return (\n Array.isArray(message.content) &&\n message.content.some((block) => 'cache_control' in block)\n );\n}\n\nfunction addCacheControlToRecentMessages<\n T extends AnthropicMessage | BaseMessage,\n>(\n messages: T[],\n maxCachePoints: number,\n canUseMessage: (message: MessageWithContent) => boolean,\n ttl?: PromptCacheTtl\n): T[] {\n if (\n !Array.isArray(messages) ||\n messages.length === 0 ||\n maxCachePoints <= 0\n ) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let cachePointsAdded = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n const hasArrayContent = Array.isArray(content);\n const canAddCache =\n cachePointsAdded < maxCachePoints &&\n canUseMessage(originalMessage) &&\n !isSyntheticMetaMessage(originalMessage);\n\n if (!canAddCache && !hasArrayContent) {\n continue;\n }\n\n let workingContent: MessageContentComplex[];\n let modified = false;\n\n if (hasArrayContent) {\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastNonEmptyTextIndex = -1;\n\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n\n if ('type' in cloned && cloned.type === 'text') {\n const text = (cloned as { text?: string }).text;\n if (text != null && text.trim() !== '') {\n lastNonEmptyTextIndex = workingContent.length;\n }\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (canAddCache && lastNonEmptyTextIndex >= 0) {\n (\n workingContent[lastNonEmptyTextIndex] as Anthropic.TextBlockParam\n ).cache_control = buildAnthropicCacheControl(ttl);\n cachePointsAdded++;\n modified = true;\n }\n\n if (!modified) {\n continue;\n }\n } else if (\n typeof content === 'string' &&\n content.trim() !== '' &&\n canAddCache\n ) {\n workingContent = [\n {\n type: 'text',\n text: content,\n cache_control: buildAnthropicCacheControl(ttl),\n },\n ] as unknown as MessageContentComplex[];\n cachePointsAdded++;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n }\n\n return updatedMessages;\n}\n\nexport function addCacheControlToStablePrefixMessages<\n T extends AnthropicMessage | BaseMessage,\n>(messages: T[], maxCachePoints: number, ttl?: PromptCacheTtl): T[] {\n const assistantMarked = addCacheControlToRecentMessages(\n messages,\n maxCachePoints,\n isAssistantConversationMessage,\n ttl\n );\n\n if (assistantMarked.some(hasCacheMarker)) {\n return assistantMarked;\n }\n\n return addCacheControlToRecentMessages(\n messages,\n maxCachePoints,\n isCacheableConversationMessage,\n ttl\n );\n}\n\n/**\n * Checks if a message's content has Anthropic cache_control fields.\n */\nfunction hasAnthropicCacheControl(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if ('cache_control' in content[i]) return true;\n }\n return false;\n}\n\n/**\n * Removes all Anthropic cache_control fields from messages\n * Used when switching from Anthropic to Bedrock provider\n * Returns a new array - only clones messages that require modification.\n */\nexport function stripAnthropicCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasAnthropicCacheControl(content)) {\n continue;\n }\n\n const clonedContent = deepCloneContent(content);\n for (let j = 0; j < clonedContent.length; j++) {\n const block = clonedContent[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n updatedMessages[i] = cloneMessage(originalMessage, clonedContent);\n }\n\n return updatedMessages;\n}\n\n/**\n * Checks if a message's content has Bedrock cachePoint blocks.\n */\nfunction hasBedrockCachePoint(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if (isCachePoint(content[i])) return true;\n }\n return false;\n}\n\n/**\n * Removes all Bedrock cachePoint blocks from messages\n * Used when switching from Bedrock to Anthropic provider\n * Returns a new array - only clones messages that require modification.\n */\nexport function stripBedrockCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasBedrockCachePoint(content)) {\n continue;\n }\n\n const clonedContent = deepCloneContent(content).filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n );\n updatedMessages[i] = cloneMessage(originalMessage, clonedContent);\n }\n\n return updatedMessages;\n}\n\n/**\n * Adds Bedrock Converse API cache points to the latest two user messages.\n * Inserts `{ cachePoint: { type: 'default' } }` as a separate content block\n * immediately after the last text block in each targeted message.\n * Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,\n * then adds fresh cache points to the latest two non-tool user messages in a single backward pass.\n * This ensures we don't accumulate stale cache points across multiple turns.\n * Returns a new array - only clones messages that require modification.\n * @param messages - The array of message objects.\n * @returns - A new array of message objects with cache points added.\n */\nexport function addBedrockCacheControl<\n T extends MessageWithContent & { getType?: () => string; role?: string },\n>(messages: T[], ttl?: PromptCacheTtl): T[] {\n if (!Array.isArray(messages) || messages.length === 0) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let cachePointsAdded = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const messageType =\n 'getType' in originalMessage &&\n typeof originalMessage.getType === 'function'\n ? originalMessage.getType()\n : undefined;\n const messageRole =\n 'role' in originalMessage && typeof originalMessage.role === 'string'\n ? originalMessage.role\n : undefined;\n\n const isSystemMessage =\n messageType === 'system' || messageRole === 'system';\n if (isSystemMessage) {\n updatedMessages[i] = sanitizeBedrockSystemMessage(originalMessage, ttl);\n continue;\n }\n\n const isToolMessage = messageType === 'tool' || messageRole === 'tool';\n const isUserMessage = messageType === 'human' || messageRole === 'user';\n const content = originalMessage.content;\n const hasSerializationProps =\n 'lc_kwargs' in originalMessage ||\n 'lc_serializable' in originalMessage ||\n 'lc_namespace' in originalMessage;\n const hasArrayContent = Array.isArray(content);\n const isEmptyString = typeof content === 'string' && content === '';\n const needsCacheAdd =\n cachePointsAdded < 2 &&\n isUserMessage &&\n !isToolMessage &&\n !isEmptyString &&\n !isSyntheticMetaMessage(originalMessage) &&\n (typeof content === 'string' || hasArrayContent);\n\n if (!needsCacheAdd && !hasArrayContent && !hasSerializationProps) {\n continue;\n }\n\n let workingContent: string | MessageContentComplex[];\n let modified = hasSerializationProps;\n\n if (hasArrayContent) {\n // Single pass: clone blocks, strip cache markers, find last\n // non-empty text block for cache point insertion — all at once.\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastNonEmptyTextIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n const type = (cloned as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (cloned as { text?: string }).text;\n if (text != null && text.trim() !== '') {\n lastNonEmptyTextIndex = workingContent.length;\n }\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (!modified && !needsCacheAdd) {\n continue;\n }\n\n // Insert cache point after the last non-empty text block.\n // Skip if no cacheable text content exists (whitespace-only messages).\n if (needsCacheAdd && lastNonEmptyTextIndex >= 0) {\n workingContent.splice(lastNonEmptyTextIndex + 1, 0, {\n cachePoint: buildBedrockCachePoint(ttl),\n } as MessageContentComplex);\n cachePointsAdded++;\n }\n } else if (typeof content === 'string' && needsCacheAdd) {\n workingContent = [\n { type: ContentTypes.TEXT, text: content },\n { cachePoint: buildBedrockCachePoint(ttl) } as MessageContentComplex,\n ];\n cachePointsAdded++;\n } else if (typeof content === 'string' && hasSerializationProps) {\n workingContent = content;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(originalMessage, workingContent);\n }\n\n return updatedMessages;\n}\n\n/**\n * Bedrock Converse API: single tail cache breakpoint (default strategy).\n *\n * The Bedrock counterpart of {@link addTailCacheControl}. Strips ALL existing\n * cache control (Bedrock cache points and Anthropic `cache_control`) from every\n * message, then inserts exactly ONE `{ cachePoint: { type: 'default' } }` block\n * immediately after the last non-empty text block of the most recent\n * non-synthetic, non-system message. Anchoring on the rolling tail keeps the\n * cached prefix append-only as the conversation grows, instead of re-writing\n * large spans every turn with the legacy \"last two user messages\" cache points.\n *\n * System messages are sanitized (Anthropic `cache_control` stripped) but never\n * anchored. Synthetic skill/meta messages are skipped as anchors so their\n * volatile content cannot pin the cache.\n *\n * Returns a new array - only clones messages that require modification.\n */\nexport function addBedrockTailCacheControl<\n T extends MessageWithContent & { getType?: () => string; role?: string },\n>(messages: T[], ttl?: PromptCacheTtl): T[] {\n if (!Array.isArray(messages) || messages.length === 0) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let cachePointPlaced = false;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const messageType =\n 'getType' in originalMessage &&\n typeof originalMessage.getType === 'function'\n ? originalMessage.getType()\n : undefined;\n const messageRole =\n 'role' in originalMessage && typeof originalMessage.role === 'string'\n ? originalMessage.role\n : undefined;\n\n const isSystemMessage =\n messageType === 'system' || messageRole === 'system';\n if (isSystemMessage) {\n updatedMessages[i] = sanitizeBedrockSystemMessage(originalMessage, ttl);\n continue;\n }\n\n const content = originalMessage.content;\n const hasSerializationProps =\n 'lc_kwargs' in originalMessage ||\n 'lc_serializable' in originalMessage ||\n 'lc_namespace' in originalMessage;\n const hasArrayContent = Array.isArray(content);\n const isEmptyString = typeof content === 'string' && content === '';\n const canPlaceCachePoint =\n !cachePointPlaced &&\n !isEmptyString &&\n !isSyntheticMetaMessage(originalMessage) &&\n (typeof content === 'string' || hasArrayContent);\n\n if (!canPlaceCachePoint && !hasArrayContent && !hasSerializationProps) {\n continue;\n }\n\n let workingContent: string | MessageContentComplex[];\n let modified = hasSerializationProps;\n\n if (hasArrayContent) {\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastNonEmptyTextIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n const type = (cloned as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (cloned as { text?: string }).text;\n if (text != null && text.trim() !== '') {\n lastNonEmptyTextIndex = workingContent.length;\n }\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (!modified && !canPlaceCachePoint) {\n continue;\n }\n\n if (canPlaceCachePoint && lastNonEmptyTextIndex >= 0) {\n workingContent.splice(lastNonEmptyTextIndex + 1, 0, {\n cachePoint: buildBedrockCachePoint(ttl),\n } as MessageContentComplex);\n cachePointPlaced = true;\n modified = true;\n }\n } else if (typeof content === 'string' && canPlaceCachePoint) {\n workingContent = [\n { type: ContentTypes.TEXT, text: content },\n { cachePoint: buildBedrockCachePoint(ttl) } as MessageContentComplex,\n ];\n cachePointPlaced = true;\n } else if (typeof content === 'string' && hasSerializationProps) {\n workingContent = content;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(originalMessage, workingContent);\n }\n\n return updatedMessages;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cache.cjs","names":["BaseMessage","toLangChainContent","withMessageRole","AIMessage","HumanMessage","SystemMessage","ToolMessage"],"sources":["../../../src/messages/cache.ts"],"sourcesContent":["import {\n AIMessage,\n BaseMessage,\n ToolMessage,\n HumanMessage,\n SystemMessage,\n MessageContentComplex,\n} from '@langchain/core/messages';\nimport type Anthropic from '@anthropic-ai/sdk';\nimport type { AnthropicMessage } from '@/types/messages';\nimport { toLangChainContent } from './langchain';\nimport { ContentTypes } from '@/common/enum';\nimport { withMessageRole } from './format';\n\ntype MessageWithContent = {\n content?: string | MessageContentComplex[];\n};\n\ntype MessageContentWithCacheControl = MessageContentComplex & {\n cache_control?: unknown;\n};\n\n/**\n * Prompt-cache breakpoint TTL.\n *\n * Both Anthropic (`cache_control.ttl`) and Bedrock (`cachePoint.ttl`) accept\n * `'5m'` (the legacy provider default) and `'1h'` (the extended cache). When\n * prompt caching is enabled the SDK now defaults to the 1-hour extended cache\n * (see {@link DEFAULT_PROMPT_CACHE_TTL}); pass `'5m'` to opt back into the\n * legacy 5-minute behavior.\n */\nexport type PromptCacheTtl = '5m' | '1h';\n\n/**\n * Default TTL applied wherever a prompt-cache breakpoint is added. The 1-hour\n * extended cache keeps prefixes warm across longer gaps between turns, at the\n * cost of a higher one-time cache-write multiplier (2x vs 1.25x for 5m).\n */\nexport const DEFAULT_PROMPT_CACHE_TTL: PromptCacheTtl = '1h';\n\n/**\n * Resolve an optionally-configured TTL to a concrete value, defaulting to the\n * 1-hour extended cache. Used at the Anthropic/Bedrock prompt-cache call sites.\n */\nexport function resolvePromptCacheTtl(\n ttl: PromptCacheTtl | undefined\n): PromptCacheTtl {\n return ttl ?? DEFAULT_PROMPT_CACHE_TTL;\n}\n\n/** Anthropic `cache_control` shape (the SDK accepts an optional `ttl`). */\ntype AnthropicCacheControl = { type: 'ephemeral'; ttl?: '1h' };\n\n/**\n * Build an Anthropic `cache_control` breakpoint for the given TTL. `'5m'` (or\n * `undefined`) omits the `ttl` field — that is the provider default, so the\n * payload stays byte-identical to the legacy 5-minute marker. `'1h'` adds the\n * explicit extended-cache `ttl`.\n */\nexport function buildAnthropicCacheControl(\n ttl?: PromptCacheTtl\n): AnthropicCacheControl {\n return ttl === '1h'\n ? { type: 'ephemeral', ttl: '1h' }\n : { type: 'ephemeral' };\n}\n\n/** Bedrock `cachePoint` shape (the SDK accepts an optional `ttl`). */\ntype BedrockCachePoint = { type: 'default'; ttl?: '1h' };\n\n/**\n * Build a Bedrock `cachePoint` for the given TTL. Mirrors\n * {@link buildAnthropicCacheControl}: `'5m'`/`undefined` omits `ttl` (the\n * legacy default), `'1h'` adds the extended-cache `ttl`.\n */\nexport function buildBedrockCachePoint(\n ttl?: PromptCacheTtl\n): BedrockCachePoint {\n return ttl === '1h' ? { type: 'default', ttl: '1h' } : { type: 'default' };\n}\n\n/**\n * A `cachePoint` under `toolConfig.tools` is only accepted on Anthropic Claude\n * models. Amazon Nova rejects it outright — `Malformed input request:\n * #/toolConfig/tools/0: extraneous key [cachePoint] is not permitted` — even\n * though it accepts `cachePoint` in `system` and `messages` (verified live:\n * Nova returns HTTP 200 with real `cacheWriteInputTokens` for both). Per the AWS\n * prompt-caching docs the support table lists `tools` for Claude only.\n *\n * Gate ONLY the tool checkpoint on this, so a `promptCache: true` config on Nova\n * keeps its valid message/system caching instead of either 400-ing (tool point)\n * or losing caching entirely. This is a capability gate (the key is rejected\n * outright), distinct from the TTL value which Bedrock downgrades gracefully.\n *\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html\n * @see https://github.com/danny-avila/LibreChat/issues/13838\n */\nexport function supportsBedrockToolCache(\n model: string | undefined | null\n): boolean {\n if (typeof model !== 'string') {\n return false;\n }\n return /claude|anthropic/i.test(model);\n}\n\n/**\n * Deep clones a message's content to prevent mutation of the original.\n */\nfunction deepCloneContent<T extends string | MessageContentComplex[]>(\n content: T\n): T {\n if (typeof content === 'string') {\n return content;\n }\n if (Array.isArray(content)) {\n return content.map((block) => ({ ...block })) as T;\n }\n return content;\n}\n\n/**\n * Clones a message with new content. For LangChain BaseMessage instances,\n * constructs a proper class instance so that `instanceof` checks are preserved\n * in downstream code (e.g., ensureThinkingBlockInMessages).\n * For plain objects (AnthropicMessage), uses object spread.\n */\nexport function cloneMessage<T extends MessageWithContent>(\n message: T,\n content: string | MessageContentComplex[]\n): T {\n if (message instanceof BaseMessage) {\n const baseParams = {\n content: toLangChainContent(content),\n additional_kwargs: { ...message.additional_kwargs },\n response_metadata: { ...message.response_metadata },\n id: message.id,\n name: message.name,\n };\n\n const msgType = message.getType();\n switch (msgType) {\n case 'ai':\n return withMessageRole(\n new AIMessage({\n ...baseParams,\n tool_calls: (message as unknown as AIMessage).tool_calls,\n }),\n 'assistant'\n ) as unknown as T;\n case 'human':\n return withMessageRole(\n new HumanMessage(baseParams),\n 'user'\n ) as unknown as T;\n case 'system':\n return withMessageRole(\n new SystemMessage(baseParams),\n 'system'\n ) as unknown as T;\n case 'tool':\n return withMessageRole(\n new ToolMessage({\n ...baseParams,\n tool_call_id: (message as unknown as ToolMessage).tool_call_id,\n }),\n 'tool'\n ) as unknown as T;\n default:\n break;\n }\n }\n\n const {\n lc_kwargs: _lc_kwargs,\n lc_serializable: _lc_serializable,\n lc_namespace: _lc_namespace,\n ...rest\n } = message as T & {\n lc_kwargs?: unknown;\n lc_serializable?: unknown;\n lc_namespace?: unknown;\n };\n\n const cloned = { ...rest, content } as T;\n\n // LangChain messages don't have a direct 'role' property - derive it from getType()\n if (\n 'getType' in message &&\n typeof message.getType === 'function' &&\n !('role' in cloned)\n ) {\n const msgType = (message as unknown as BaseMessage).getType();\n const roleMap: Record<string, string> = {\n human: 'user',\n ai: 'assistant',\n system: 'system',\n tool: 'tool',\n };\n (cloned as Record<string, unknown>).role = roleMap[msgType] || msgType;\n }\n\n return cloned;\n}\n\n/**\n * Sanitize a Bedrock system message: strip Anthropic `cache_control` (Bedrock\n * conversion can't use it) and normalize any existing `cachePoint` to the\n * resolved TTL. The normalization matters because Bedrock requires longer-TTL\n * checkpoints to appear before shorter ones — a stale 5-minute system cachePoint\n * (host-supplied or carried over from a 5m config) left ahead of a 1-hour\n * message tail would make the request invalid. System messages are never\n * anchored as the tail breakpoint; this only fixes markers already present.\n */\nfunction sanitizeBedrockSystemMessage<T extends MessageWithContent>(\n message: T,\n ttl?: PromptCacheTtl\n): T {\n const content = message.content;\n if (!Array.isArray(content)) {\n return message;\n }\n\n const sanitized: MessageContentComplex[] = [];\n let modified = false;\n for (const block of content) {\n if (isCachePoint(block)) {\n const existing = (block as { cachePoint?: { ttl?: unknown } }).cachePoint;\n const desired = buildBedrockCachePoint(ttl);\n if (existing?.ttl !== desired.ttl) {\n modified = true;\n sanitized.push({ cachePoint: desired } as MessageContentComplex);\n } else {\n sanitized.push(block);\n }\n continue;\n }\n if ('cache_control' in block) {\n const cloned: MessageContentWithCacheControl = { ...block };\n delete cloned.cache_control;\n modified = true;\n sanitized.push(cloned);\n continue;\n }\n sanitized.push(block);\n }\n\n if (!modified) {\n return message;\n }\n\n return cloneMessage(message, sanitized);\n}\n\n/**\n * Anthropic API: Adds cache control to the appropriate user messages in the payload.\n * Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,\n * then adds fresh cache control to the last 2 user messages in a single backward pass.\n * This ensures we don't accumulate stale cache points across multiple turns.\n * Returns a new array - only clones messages that require modification.\n * @param messages - The array of message objects.\n * @returns - A new array of message objects with cache control added.\n */\nexport function addCacheControl<T extends AnthropicMessage | BaseMessage>(\n messages: T[],\n ttl?: PromptCacheTtl\n): T[] {\n if (!Array.isArray(messages) || messages.length < 2) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let userMessagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n const isUserMessage =\n ('getType' in originalMessage && originalMessage.getType() === 'human') ||\n ('role' in originalMessage && originalMessage.role === 'user');\n const hasArrayContent = Array.isArray(content);\n const needsCacheAdd =\n userMessagesModified < 2 &&\n isUserMessage &&\n !isSyntheticMetaMessage(originalMessage) &&\n (typeof content === 'string' || hasArrayContent);\n\n // Skip messages that don't need any work\n if (!needsCacheAdd && !hasArrayContent) {\n continue;\n }\n\n let workingContent: MessageContentComplex[];\n let modified = false;\n\n if (hasArrayContent) {\n // Single pass: clone blocks, strip cache markers and cache points,\n // find last text block index for cache insertion — all at once.\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastTextIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue; // skip cache point blocks\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n if ('type' in cloned && cloned.type === 'text') {\n lastTextIndex = workingContent.length;\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (!modified && !needsCacheAdd) {\n continue; // nothing to strip and no cache to add\n }\n\n // Add cache control to the last text block for user messages\n if (needsCacheAdd && lastTextIndex >= 0) {\n (\n workingContent[lastTextIndex] as Anthropic.TextBlockParam\n ).cache_control = buildAnthropicCacheControl(ttl);\n userMessagesModified++;\n }\n } else if (typeof content === 'string' && needsCacheAdd) {\n workingContent = [\n {\n type: 'text',\n text: content,\n cache_control: buildAnthropicCacheControl(ttl),\n },\n ] as unknown as MessageContentComplex[];\n userMessagesModified++;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n }\n\n return updatedMessages;\n}\n\n/**\n * Checks if a content block is a cache point\n */\nfunction isCachePoint(block: MessageContentComplex): boolean {\n return 'cachePoint' in block && !('type' in block);\n}\n\n/**\n * Block types that must never anchor the tail cache breakpoint, because the\n * marker would not survive to the model call:\n * - `thinking` / `redacted_thinking`: native Anthropic reasoning — the API\n * rejects `cache_control` on these blocks.\n * - `reasoning_content` / `reasoning` / `think`: foreign reasoning (Bedrock,\n * Google, LibreChat) that `_convertMessagesToAnthropicPayload` DROPS on\n * assistant turns during a cross-provider handoff.\n * - `input_json_delta`: persisted partial tool-input deltas, also DROPPED by\n * `_convertMessagesToAnthropicPayload` (the assembled input is restored onto\n * the tool_use block).\n * Anchoring the only breakpoint on a block that is about to disappear silently\n * loses tail caching, so all of these are excluded.\n */\nconst NON_ANCHORABLE_BLOCK_TYPES = new Set([\n 'thinking',\n 'redacted_thinking',\n 'reasoning_content',\n 'reasoning',\n 'think',\n 'input_json_delta',\n]);\n\n/**\n * A block can anchor the tail cache breakpoint when it is a real content block\n * that the Anthropic API accepts `cache_control` on and that survives provider\n * conversion. Reasoning / dropped-delta blocks are excluded (see\n * {@link NON_ANCHORABLE_BLOCK_TYPES}), and empty text blocks are not cacheable,\n * so both are skipped.\n */\nfunction isTailCacheableBlock(block: MessageContentComplex): boolean {\n if (isCachePoint(block)) {\n return false;\n }\n const type = (block as { type?: string }).type;\n if (type == null || NON_ANCHORABLE_BLOCK_TYPES.has(type)) {\n return false;\n }\n if (type === 'text') {\n const text = (block as { text?: string }).text;\n return text != null && text.trim() !== '';\n }\n return true;\n}\n\n/**\n * Anthropic API: single tail cache breakpoint (default strategy).\n *\n * Places exactly ONE `cache_control` marker on the last cacheable block of the\n * final non-synthetic message, mirroring the Claude Code strategy\n * (`markerIndex = messages.length - 1`). Because the marker always rides the\n * true tail, the entire conversation prefix is written once and read back on\n * the next turn as the history grows append-only — instead of the rolling\n * \"last two user messages\" markers, which leave freshly appended tool/assistant\n * turns outside the cached prefix and re-write large spans every step.\n *\n * Stale markers (Anthropic `cache_control` and Bedrock cache points) are\n * stripped from every message in a single backward pass so exactly one marker\n * survives. Synthetic skill/meta messages are skipped as anchors (their volatile\n * content must not pin the cache) but still have stale markers removed.\n *\n * Returns a new array; only messages that require modification are cloned.\n */\nexport function addTailCacheControl<T extends AnthropicMessage | BaseMessage>(\n messages: T[],\n ttl?: PromptCacheTtl\n): T[] {\n if (!Array.isArray(messages) || messages.length === 0) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let markerPlaced = false;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n const hasArrayContent = Array.isArray(content);\n const canPlaceMarker =\n !markerPlaced && !isSyntheticMetaMessage(originalMessage);\n\n // Earlier string-content messages carry no markers to strip.\n if (!canPlaceMarker && !hasArrayContent) {\n continue;\n }\n\n let workingContent: MessageContentComplex[];\n let modified = false;\n\n if (hasArrayContent) {\n const src = content as MessageContentComplex[];\n workingContent = [];\n let tailIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n if (\n canPlaceMarker &&\n isTailCacheableBlock(cloned as MessageContentComplex)\n ) {\n tailIndex = workingContent.length;\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (canPlaceMarker && tailIndex >= 0) {\n (workingContent[tailIndex] as Anthropic.TextBlockParam).cache_control =\n buildAnthropicCacheControl(ttl);\n markerPlaced = true;\n modified = true;\n }\n\n if (!modified) {\n continue;\n }\n } else if (\n typeof content === 'string' &&\n canPlaceMarker &&\n content.trim() !== ''\n ) {\n workingContent = [\n {\n type: 'text',\n text: content,\n cache_control: buildAnthropicCacheControl(ttl),\n },\n ] as unknown as MessageContentComplex[];\n markerPlaced = true;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n }\n\n return updatedMessages;\n}\n\nfunction getMessageRole(message: MessageWithContent): string | undefined {\n if (message instanceof BaseMessage) {\n return message.getType();\n }\n if ('role' in message && typeof message.role === 'string') {\n return message.role;\n }\n return undefined;\n}\n\nconst SKILL_MESSAGE_SOURCE = 'skill';\n\n/**\n * Synthetic skill/meta messages (reconstructed skill bodies, primed SKILL.md\n * instructions) are re-injected every turn and are not stable conversation\n * turns. They must not anchor a fresh prompt-cache marker — doing so pins the\n * cache to a volatile/duplicated prefix. Stale markers are still stripped from\n * them; only the *adding* of new markers is suppressed. Detected via\n * `additional_kwargs.isMeta === true` or `additional_kwargs.source === 'skill'`.\n */\nfunction isSyntheticMetaMessage(message: MessageWithContent): boolean {\n const { additional_kwargs: kwargs } = message as {\n additional_kwargs?: { isMeta?: unknown; source?: unknown };\n };\n if (kwargs == null) {\n return false;\n }\n return kwargs.isMeta === true || kwargs.source === SKILL_MESSAGE_SOURCE;\n}\n\nfunction isCacheableConversationMessage(message: MessageWithContent): boolean {\n const role = getMessageRole(message);\n return (\n role === 'human' || role === 'user' || role === 'ai' || role === 'assistant'\n );\n}\n\nfunction isAssistantConversationMessage(message: MessageWithContent): boolean {\n const role = getMessageRole(message);\n return role === 'ai' || role === 'assistant';\n}\n\nfunction hasCacheMarker(message: MessageWithContent): boolean {\n return (\n Array.isArray(message.content) &&\n message.content.some((block) => 'cache_control' in block)\n );\n}\n\nfunction addCacheControlToRecentMessages<\n T extends AnthropicMessage | BaseMessage,\n>(\n messages: T[],\n maxCachePoints: number,\n canUseMessage: (message: MessageWithContent) => boolean,\n ttl?: PromptCacheTtl\n): T[] {\n if (\n !Array.isArray(messages) ||\n messages.length === 0 ||\n maxCachePoints <= 0\n ) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let cachePointsAdded = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n const hasArrayContent = Array.isArray(content);\n const canAddCache =\n cachePointsAdded < maxCachePoints &&\n canUseMessage(originalMessage) &&\n !isSyntheticMetaMessage(originalMessage);\n\n if (!canAddCache && !hasArrayContent) {\n continue;\n }\n\n let workingContent: MessageContentComplex[];\n let modified = false;\n\n if (hasArrayContent) {\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastNonEmptyTextIndex = -1;\n\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n\n if ('type' in cloned && cloned.type === 'text') {\n const text = (cloned as { text?: string }).text;\n if (text != null && text.trim() !== '') {\n lastNonEmptyTextIndex = workingContent.length;\n }\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (canAddCache && lastNonEmptyTextIndex >= 0) {\n (\n workingContent[lastNonEmptyTextIndex] as Anthropic.TextBlockParam\n ).cache_control = buildAnthropicCacheControl(ttl);\n cachePointsAdded++;\n modified = true;\n }\n\n if (!modified) {\n continue;\n }\n } else if (\n typeof content === 'string' &&\n content.trim() !== '' &&\n canAddCache\n ) {\n workingContent = [\n {\n type: 'text',\n text: content,\n cache_control: buildAnthropicCacheControl(ttl),\n },\n ] as unknown as MessageContentComplex[];\n cachePointsAdded++;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n }\n\n return updatedMessages;\n}\n\nexport function addCacheControlToStablePrefixMessages<\n T extends AnthropicMessage | BaseMessage,\n>(messages: T[], maxCachePoints: number, ttl?: PromptCacheTtl): T[] {\n const assistantMarked = addCacheControlToRecentMessages(\n messages,\n maxCachePoints,\n isAssistantConversationMessage,\n ttl\n );\n\n if (assistantMarked.some(hasCacheMarker)) {\n return assistantMarked;\n }\n\n return addCacheControlToRecentMessages(\n messages,\n maxCachePoints,\n isCacheableConversationMessage,\n ttl\n );\n}\n\n/**\n * Checks if a message's content has Anthropic cache_control fields.\n */\nfunction hasAnthropicCacheControl(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if ('cache_control' in content[i]) return true;\n }\n return false;\n}\n\n/**\n * Removes all Anthropic cache_control fields from messages\n * Used when switching from Anthropic to Bedrock provider\n * Returns a new array - only clones messages that require modification.\n */\nexport function stripAnthropicCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasAnthropicCacheControl(content)) {\n continue;\n }\n\n const clonedContent = deepCloneContent(content);\n for (let j = 0; j < clonedContent.length; j++) {\n const block = clonedContent[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n updatedMessages[i] = cloneMessage(originalMessage, clonedContent);\n }\n\n return updatedMessages;\n}\n\n/**\n * Checks if a message's content has Bedrock cachePoint blocks.\n */\nfunction hasBedrockCachePoint(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if (isCachePoint(content[i])) return true;\n }\n return false;\n}\n\n/**\n * Removes all Bedrock cachePoint blocks from messages\n * Used when switching from Bedrock to Anthropic provider\n * Returns a new array - only clones messages that require modification.\n */\nexport function stripBedrockCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasBedrockCachePoint(content)) {\n continue;\n }\n\n const clonedContent = deepCloneContent(content).filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n );\n updatedMessages[i] = cloneMessage(originalMessage, clonedContent);\n }\n\n return updatedMessages;\n}\n\n/**\n * Adds Bedrock Converse API cache points to the latest two user messages.\n * Inserts `{ cachePoint: { type: 'default' } }` as a separate content block\n * immediately after the last text block in each targeted message.\n * Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,\n * then adds fresh cache points to the latest two non-tool user messages in a single backward pass.\n * This ensures we don't accumulate stale cache points across multiple turns.\n * Returns a new array - only clones messages that require modification.\n * @param messages - The array of message objects.\n * @returns - A new array of message objects with cache points added.\n */\nexport function addBedrockCacheControl<\n T extends MessageWithContent & { getType?: () => string; role?: string },\n>(messages: T[], ttl?: PromptCacheTtl): T[] {\n if (!Array.isArray(messages) || messages.length === 0) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let cachePointsAdded = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const messageType =\n 'getType' in originalMessage &&\n typeof originalMessage.getType === 'function'\n ? originalMessage.getType()\n : undefined;\n const messageRole =\n 'role' in originalMessage && typeof originalMessage.role === 'string'\n ? originalMessage.role\n : undefined;\n\n const isSystemMessage =\n messageType === 'system' || messageRole === 'system';\n if (isSystemMessage) {\n updatedMessages[i] = sanitizeBedrockSystemMessage(originalMessage, ttl);\n continue;\n }\n\n const isToolMessage = messageType === 'tool' || messageRole === 'tool';\n const isUserMessage = messageType === 'human' || messageRole === 'user';\n const content = originalMessage.content;\n const hasSerializationProps =\n 'lc_kwargs' in originalMessage ||\n 'lc_serializable' in originalMessage ||\n 'lc_namespace' in originalMessage;\n const hasArrayContent = Array.isArray(content);\n const isEmptyString = typeof content === 'string' && content === '';\n const needsCacheAdd =\n cachePointsAdded < 2 &&\n isUserMessage &&\n !isToolMessage &&\n !isEmptyString &&\n !isSyntheticMetaMessage(originalMessage) &&\n (typeof content === 'string' || hasArrayContent);\n\n if (!needsCacheAdd && !hasArrayContent && !hasSerializationProps) {\n continue;\n }\n\n let workingContent: string | MessageContentComplex[];\n let modified = hasSerializationProps;\n\n if (hasArrayContent) {\n // Single pass: clone blocks, strip cache markers, find last\n // non-empty text block for cache point insertion — all at once.\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastNonEmptyTextIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n const type = (cloned as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (cloned as { text?: string }).text;\n if (text != null && text.trim() !== '') {\n lastNonEmptyTextIndex = workingContent.length;\n }\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (!modified && !needsCacheAdd) {\n continue;\n }\n\n // Insert cache point after the last non-empty text block.\n // Skip if no cacheable text content exists (whitespace-only messages).\n if (needsCacheAdd && lastNonEmptyTextIndex >= 0) {\n workingContent.splice(lastNonEmptyTextIndex + 1, 0, {\n cachePoint: buildBedrockCachePoint(ttl),\n } as MessageContentComplex);\n cachePointsAdded++;\n }\n } else if (typeof content === 'string' && needsCacheAdd) {\n workingContent = [\n { type: ContentTypes.TEXT, text: content },\n { cachePoint: buildBedrockCachePoint(ttl) } as MessageContentComplex,\n ];\n cachePointsAdded++;\n } else if (typeof content === 'string' && hasSerializationProps) {\n workingContent = content;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(originalMessage, workingContent);\n }\n\n return updatedMessages;\n}\n\n/**\n * Bedrock Converse API: single tail cache breakpoint (default strategy).\n *\n * The Bedrock counterpart of {@link addTailCacheControl}. Strips ALL existing\n * cache control (Bedrock cache points and Anthropic `cache_control`) from every\n * message, then inserts exactly ONE `{ cachePoint: { type: 'default' } }` block\n * immediately after the last non-empty text block of the most recent\n * non-synthetic, non-system message. Anchoring on the rolling tail keeps the\n * cached prefix append-only as the conversation grows, instead of re-writing\n * large spans every turn with the legacy \"last two user messages\" cache points.\n *\n * System messages are sanitized (Anthropic `cache_control` stripped) but never\n * anchored. Synthetic skill/meta messages are skipped as anchors so their\n * volatile content cannot pin the cache.\n *\n * Returns a new array - only clones messages that require modification.\n */\nexport function addBedrockTailCacheControl<\n T extends MessageWithContent & { getType?: () => string; role?: string },\n>(messages: T[], ttl?: PromptCacheTtl): T[] {\n if (!Array.isArray(messages) || messages.length === 0) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let cachePointPlaced = false;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const messageType =\n 'getType' in originalMessage &&\n typeof originalMessage.getType === 'function'\n ? originalMessage.getType()\n : undefined;\n const messageRole =\n 'role' in originalMessage && typeof originalMessage.role === 'string'\n ? originalMessage.role\n : undefined;\n\n const isSystemMessage =\n messageType === 'system' || messageRole === 'system';\n if (isSystemMessage) {\n updatedMessages[i] = sanitizeBedrockSystemMessage(originalMessage, ttl);\n continue;\n }\n\n const content = originalMessage.content;\n const hasSerializationProps =\n 'lc_kwargs' in originalMessage ||\n 'lc_serializable' in originalMessage ||\n 'lc_namespace' in originalMessage;\n const hasArrayContent = Array.isArray(content);\n const isEmptyString = typeof content === 'string' && content === '';\n const canPlaceCachePoint =\n !cachePointPlaced &&\n !isEmptyString &&\n !isSyntheticMetaMessage(originalMessage) &&\n (typeof content === 'string' || hasArrayContent);\n\n if (!canPlaceCachePoint && !hasArrayContent && !hasSerializationProps) {\n continue;\n }\n\n let workingContent: string | MessageContentComplex[];\n let modified = hasSerializationProps;\n\n if (hasArrayContent) {\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastNonEmptyTextIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n const type = (cloned as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (cloned as { text?: string }).text;\n if (text != null && text.trim() !== '') {\n lastNonEmptyTextIndex = workingContent.length;\n }\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (!modified && !canPlaceCachePoint) {\n continue;\n }\n\n if (canPlaceCachePoint && lastNonEmptyTextIndex >= 0) {\n workingContent.splice(lastNonEmptyTextIndex + 1, 0, {\n cachePoint: buildBedrockCachePoint(ttl),\n } as MessageContentComplex);\n cachePointPlaced = true;\n modified = true;\n }\n } else if (typeof content === 'string' && canPlaceCachePoint) {\n workingContent = [\n { type: ContentTypes.TEXT, text: content },\n { cachePoint: buildBedrockCachePoint(ttl) } as MessageContentComplex,\n ];\n cachePointPlaced = true;\n } else if (typeof content === 'string' && hasSerializationProps) {\n workingContent = content;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(originalMessage, workingContent);\n }\n\n return updatedMessages;\n}\n"],"mappings":"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"}
|
|
@@ -19,11 +19,11 @@ CONSTRAINTS:
|
|
|
19
19
|
- Do not guess file paths. Use paths from the skill documentation or tool output.`;
|
|
20
20
|
const ReadFileToolSchema = {
|
|
21
21
|
type: "object",
|
|
22
|
-
properties: {
|
|
22
|
+
properties: { path: {
|
|
23
23
|
type: "string",
|
|
24
24
|
description: "Path to the file. For skill files: \"{skillName}/{path}\" (e.g. \"pdf-analyzer/src/utils.py\"). For code execution output: the path as returned by the execution tool."
|
|
25
25
|
} },
|
|
26
|
-
required: ["
|
|
26
|
+
required: ["path"]
|
|
27
27
|
};
|
|
28
28
|
const ReadFileToolDefinition = {
|
|
29
29
|
name: ReadFileToolName,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReadFile.cjs","names":[],"sources":["../../../src/tools/ReadFile.ts"],"sourcesContent":["// src/tools/ReadFile.ts\nimport { Constants } from '@/common';\n\nexport const ReadFileToolName = Constants.READ_FILE;\n\nexport const ReadFileToolDescription = `Read the contents of a file. Returns text content with line numbers for easy reference.\n\nFor skill files, use the path format: {skillName}/{filePath} (e.g. \"pdf-analyzer/src/utils.py\", \"code-review/SKILL.md\").\n\nBEHAVIOR:\n- Text files: returned with numbered lines.\n- Images (png, jpeg, gif, webp): returned as visual content the model can see.\n- PDFs: returned as document content.\n- Other binary files: metadata returned with a note to use bash for processing.\n- Large files (>256KB text, >10MB binary): metadata only.\n- SKILL.md: returns the skill's instructions directly.\n\nCONSTRAINTS:\n- Only files from invoked skills or code execution output are accessible.\n- Do not guess file paths. Use paths from the skill documentation or tool output.`;\n\nexport const ReadFileToolSchema = {\n type: 'object',\n properties: {\n
|
|
1
|
+
{"version":3,"file":"ReadFile.cjs","names":[],"sources":["../../../src/tools/ReadFile.ts"],"sourcesContent":["// src/tools/ReadFile.ts\nimport { Constants } from '@/common';\n\nexport const ReadFileToolName = Constants.READ_FILE;\n\nexport const ReadFileToolDescription = `Read the contents of a file. Returns text content with line numbers for easy reference.\n\nFor skill files, use the path format: {skillName}/{filePath} (e.g. \"pdf-analyzer/src/utils.py\", \"code-review/SKILL.md\").\n\nBEHAVIOR:\n- Text files: returned with numbered lines.\n- Images (png, jpeg, gif, webp): returned as visual content the model can see.\n- PDFs: returned as document content.\n- Other binary files: metadata returned with a note to use bash for processing.\n- Large files (>256KB text, >10MB binary): metadata only.\n- SKILL.md: returns the skill's instructions directly.\n\nCONSTRAINTS:\n- Only files from invoked skills or code execution output are accessible.\n- Do not guess file paths. Use paths from the skill documentation or tool output.`;\n\nexport const ReadFileToolSchema = {\n type: 'object',\n properties: {\n path: {\n type: 'string',\n description:\n 'Path to the file. For skill files: \"{skillName}/{path}\" (e.g. \"pdf-analyzer/src/utils.py\"). For code execution output: the path as returned by the execution tool.',\n },\n },\n required: ['path'],\n} as const;\n\nexport const ReadFileToolDefinition = {\n name: ReadFileToolName,\n description: ReadFileToolDescription,\n parameters: ReadFileToolSchema,\n responseFormat: 'content_and_artifact' as const,\n} as const;\n"],"mappings":";;;AAGA,MAAa,mBAAA;AAEb,MAAa,0BAA0B;;;;;;;;;;;;;;;AAgBvC,MAAa,qBAAqB;CAChC,MAAM;CACN,YAAY,EACV,MAAM;EACJ,MAAM;EACN,aACE;CACJ,EACF;CACA,UAAU,CAAC,MAAM;AACnB;AAEA,MAAa,yBAAyB;CACpC,MAAM;CACN,aAAa;CACb,YAAY;CACZ,gBAAgB;AAClB"}
|