@librechat/agents 3.1.77 → 3.1.78

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.
Files changed (185) hide show
  1. package/dist/cjs/common/enum.cjs +54 -0
  2. package/dist/cjs/common/enum.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +155 -4
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +291 -0
  6. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs.map +1 -0
  7. package/dist/cjs/main.cjs +90 -0
  8. package/dist/cjs/main.cjs.map +1 -1
  9. package/dist/cjs/messages/anthropicToolCache.cjs +102 -0
  10. package/dist/cjs/messages/anthropicToolCache.cjs.map +1 -0
  11. package/dist/cjs/messages/prune.cjs +27 -0
  12. package/dist/cjs/messages/prune.cjs.map +1 -1
  13. package/dist/cjs/messages/recency.cjs +99 -0
  14. package/dist/cjs/messages/recency.cjs.map +1 -0
  15. package/dist/cjs/run.cjs +30 -0
  16. package/dist/cjs/run.cjs.map +1 -1
  17. package/dist/cjs/summarization/node.cjs +100 -6
  18. package/dist/cjs/summarization/node.cjs.map +1 -1
  19. package/dist/cjs/tools/ToolNode.cjs +635 -23
  20. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  21. package/dist/cjs/tools/local/CompileCheckTool.cjs +227 -0
  22. package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -0
  23. package/dist/cjs/tools/local/FileCheckpointer.cjs +90 -0
  24. package/dist/cjs/tools/local/FileCheckpointer.cjs.map +1 -0
  25. package/dist/cjs/tools/local/LocalCodingTools.cjs +1098 -0
  26. package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -0
  27. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +1042 -0
  28. package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -0
  29. package/dist/cjs/tools/local/LocalExecutionTools.cjs +122 -0
  30. package/dist/cjs/tools/local/LocalExecutionTools.cjs.map +1 -0
  31. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +453 -0
  32. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -0
  33. package/dist/cjs/tools/local/attachments.cjs +183 -0
  34. package/dist/cjs/tools/local/attachments.cjs.map +1 -0
  35. package/dist/cjs/tools/local/bashAst.cjs +129 -0
  36. package/dist/cjs/tools/local/bashAst.cjs.map +1 -0
  37. package/dist/cjs/tools/local/editStrategies.cjs +188 -0
  38. package/dist/cjs/tools/local/editStrategies.cjs.map +1 -0
  39. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +141 -0
  40. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -0
  41. package/dist/cjs/tools/local/syntaxCheck.cjs +182 -0
  42. package/dist/cjs/tools/local/syntaxCheck.cjs.map +1 -0
  43. package/dist/cjs/tools/local/textEncoding.cjs +30 -0
  44. package/dist/cjs/tools/local/textEncoding.cjs.map +1 -0
  45. package/dist/cjs/tools/local/workspaceFS.cjs +51 -0
  46. package/dist/cjs/tools/local/workspaceFS.cjs.map +1 -0
  47. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +31 -0
  48. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  49. package/dist/esm/common/enum.mjs +53 -1
  50. package/dist/esm/common/enum.mjs.map +1 -1
  51. package/dist/esm/graphs/Graph.mjs +156 -5
  52. package/dist/esm/graphs/Graph.mjs.map +1 -1
  53. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +289 -0
  54. package/dist/esm/hooks/createWorkspacePolicyHook.mjs.map +1 -0
  55. package/dist/esm/main.mjs +17 -2
  56. package/dist/esm/main.mjs.map +1 -1
  57. package/dist/esm/messages/anthropicToolCache.mjs +99 -0
  58. package/dist/esm/messages/anthropicToolCache.mjs.map +1 -0
  59. package/dist/esm/messages/prune.mjs +26 -1
  60. package/dist/esm/messages/prune.mjs.map +1 -1
  61. package/dist/esm/messages/recency.mjs +97 -0
  62. package/dist/esm/messages/recency.mjs.map +1 -0
  63. package/dist/esm/run.mjs +30 -0
  64. package/dist/esm/run.mjs.map +1 -1
  65. package/dist/esm/summarization/node.mjs +100 -6
  66. package/dist/esm/summarization/node.mjs.map +1 -1
  67. package/dist/esm/tools/ToolNode.mjs +635 -23
  68. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  69. package/dist/esm/tools/local/CompileCheckTool.mjs +223 -0
  70. package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -0
  71. package/dist/esm/tools/local/FileCheckpointer.mjs +87 -0
  72. package/dist/esm/tools/local/FileCheckpointer.mjs.map +1 -0
  73. package/dist/esm/tools/local/LocalCodingTools.mjs +1075 -0
  74. package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -0
  75. package/dist/esm/tools/local/LocalExecutionEngine.mjs +1022 -0
  76. package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -0
  77. package/dist/esm/tools/local/LocalExecutionTools.mjs +117 -0
  78. package/dist/esm/tools/local/LocalExecutionTools.mjs.map +1 -0
  79. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +448 -0
  80. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -0
  81. package/dist/esm/tools/local/attachments.mjs +180 -0
  82. package/dist/esm/tools/local/attachments.mjs.map +1 -0
  83. package/dist/esm/tools/local/bashAst.mjs +126 -0
  84. package/dist/esm/tools/local/bashAst.mjs.map +1 -0
  85. package/dist/esm/tools/local/editStrategies.mjs +185 -0
  86. package/dist/esm/tools/local/editStrategies.mjs.map +1 -0
  87. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +137 -0
  88. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -0
  89. package/dist/esm/tools/local/syntaxCheck.mjs +179 -0
  90. package/dist/esm/tools/local/syntaxCheck.mjs.map +1 -0
  91. package/dist/esm/tools/local/textEncoding.mjs +27 -0
  92. package/dist/esm/tools/local/textEncoding.mjs.map +1 -0
  93. package/dist/esm/tools/local/workspaceFS.mjs +49 -0
  94. package/dist/esm/tools/local/workspaceFS.mjs.map +1 -0
  95. package/dist/esm/tools/subagent/SubagentExecutor.mjs +31 -0
  96. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  97. package/dist/types/common/enum.d.ts +39 -1
  98. package/dist/types/graphs/Graph.d.ts +34 -0
  99. package/dist/types/hooks/createWorkspacePolicyHook.d.ts +95 -0
  100. package/dist/types/hooks/index.d.ts +2 -0
  101. package/dist/types/index.d.ts +1 -0
  102. package/dist/types/messages/anthropicToolCache.d.ts +51 -0
  103. package/dist/types/messages/index.d.ts +2 -0
  104. package/dist/types/messages/prune.d.ts +11 -0
  105. package/dist/types/messages/recency.d.ts +64 -0
  106. package/dist/types/run.d.ts +21 -0
  107. package/dist/types/tools/ToolNode.d.ts +145 -2
  108. package/dist/types/tools/local/CompileCheckTool.d.ts +31 -0
  109. package/dist/types/tools/local/FileCheckpointer.d.ts +39 -0
  110. package/dist/types/tools/local/LocalCodingTools.d.ts +57 -0
  111. package/dist/types/tools/local/LocalExecutionEngine.d.ts +149 -0
  112. package/dist/types/tools/local/LocalExecutionTools.d.ts +9 -0
  113. package/dist/types/tools/local/LocalProgrammaticToolCalling.d.ts +21 -0
  114. package/dist/types/tools/local/attachments.d.ts +84 -0
  115. package/dist/types/tools/local/bashAst.d.ts +11 -0
  116. package/dist/types/tools/local/editStrategies.d.ts +28 -0
  117. package/dist/types/tools/local/index.d.ts +12 -0
  118. package/dist/types/tools/local/resolveLocalExecutionTools.d.ts +38 -0
  119. package/dist/types/tools/local/syntaxCheck.d.ts +42 -0
  120. package/dist/types/tools/local/textEncoding.d.ts +21 -0
  121. package/dist/types/tools/local/workspaceFS.d.ts +49 -0
  122. package/dist/types/tools/subagent/SubagentExecutor.d.ts +29 -0
  123. package/dist/types/types/hitl.d.ts +56 -27
  124. package/dist/types/types/run.d.ts +8 -1
  125. package/dist/types/types/summarize.d.ts +30 -0
  126. package/dist/types/types/tools.d.ts +341 -6
  127. package/package.json +21 -2
  128. package/src/common/enum.ts +54 -0
  129. package/src/graphs/Graph.ts +173 -6
  130. package/src/hooks/__tests__/compactHooks.test.ts +38 -2
  131. package/src/hooks/__tests__/createWorkspacePolicyHook.test.ts +393 -0
  132. package/src/hooks/createWorkspacePolicyHook.ts +355 -0
  133. package/src/hooks/index.ts +6 -0
  134. package/src/index.ts +1 -0
  135. package/src/messages/__tests__/anthropicToolCache.test.ts +125 -0
  136. package/src/messages/__tests__/recency.test.ts +267 -0
  137. package/src/messages/anthropicToolCache.ts +116 -0
  138. package/src/messages/index.ts +2 -0
  139. package/src/messages/prune.ts +27 -1
  140. package/src/messages/recency.ts +155 -0
  141. package/src/run.ts +31 -0
  142. package/src/scripts/compare_pi_vs_ours.ts +840 -0
  143. package/src/scripts/local_engine.ts +166 -0
  144. package/src/scripts/local_engine_checkpointer.ts +205 -0
  145. package/src/scripts/local_engine_compile.ts +263 -0
  146. package/src/scripts/local_engine_hooks.ts +226 -0
  147. package/src/scripts/local_engine_image.ts +201 -0
  148. package/src/scripts/local_engine_ptc.ts +151 -0
  149. package/src/scripts/local_engine_workspace.ts +258 -0
  150. package/src/scripts/subagent-configurable-inheritance.ts +252 -0
  151. package/src/scripts/summarization-recency.ts +462 -0
  152. package/src/specs/prune.test.ts +39 -0
  153. package/src/summarization/__tests__/node.test.ts +499 -3
  154. package/src/summarization/node.ts +124 -7
  155. package/src/tools/ToolNode.ts +769 -20
  156. package/src/tools/__tests__/LocalExecutionTools.test.ts +2647 -0
  157. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +175 -0
  158. package/src/tools/__tests__/SubagentExecutor.test.ts +148 -0
  159. package/src/tools/__tests__/ToolNode.outputReferences.test.ts +114 -0
  160. package/src/tools/__tests__/ToolNode.session.test.ts +84 -0
  161. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +467 -0
  162. package/src/tools/__tests__/directToolHooks.test.ts +411 -0
  163. package/src/tools/__tests__/localToolNames.test.ts +73 -0
  164. package/src/tools/__tests__/workspaceSeam.test.ts +134 -0
  165. package/src/tools/local/CompileCheckTool.ts +278 -0
  166. package/src/tools/local/FileCheckpointer.ts +93 -0
  167. package/src/tools/local/LocalCodingTools.ts +1342 -0
  168. package/src/tools/local/LocalExecutionEngine.ts +1329 -0
  169. package/src/tools/local/LocalExecutionTools.ts +167 -0
  170. package/src/tools/local/LocalProgrammaticToolCalling.ts +594 -0
  171. package/src/tools/local/__tests__/FileCheckpointer.test.ts +120 -0
  172. package/src/tools/local/__tests__/editStrategies.test.ts +134 -0
  173. package/src/tools/local/attachments.ts +251 -0
  174. package/src/tools/local/bashAst.ts +151 -0
  175. package/src/tools/local/editStrategies.ts +188 -0
  176. package/src/tools/local/index.ts +12 -0
  177. package/src/tools/local/resolveLocalExecutionTools.ts +208 -0
  178. package/src/tools/local/syntaxCheck.ts +243 -0
  179. package/src/tools/local/textEncoding.ts +37 -0
  180. package/src/tools/local/workspaceFS.ts +89 -0
  181. package/src/tools/subagent/SubagentExecutor.ts +60 -0
  182. package/src/types/hitl.ts +56 -27
  183. package/src/types/run.ts +12 -1
  184. package/src/types/summarize.ts +31 -0
  185. package/src/types/tools.ts +359 -7
@@ -0,0 +1,291 @@
1
+ 'use strict';
2
+
3
+ var os = require('os');
4
+ var path = require('path');
5
+ var promises = require('fs/promises');
6
+ var _enum = require('../common/enum.cjs');
7
+
8
+ /**
9
+ * Workspace boundary policy as a `PreToolUse` hook.
10
+ *
11
+ * Local-engine file tools enforce a hard workspace boundary at the
12
+ * tool implementation layer (`resolveWorkspacePathSafe`). This hook
13
+ * adds a complementary, host-controlled layer on top that uses the
14
+ * standard PreToolUse / HITL machinery to *negotiate* access to
15
+ * paths outside the workspace — instead of just throwing.
16
+ *
17
+ * The host opts in by registering this hook on a `HookRegistry`; the
18
+ * hook inspects each tool call's input, extracts the file paths it
19
+ * mentions via per-tool extractors, and returns:
20
+ *
21
+ * - `allow` — every path is inside `workspace.root`
22
+ * (or `additionalRoots`)
23
+ * - `deny` — at least one path is outside, and the
24
+ * configured outside-policy is `'deny'`
25
+ * - `ask` — at least one path is outside, and the
26
+ * outside-policy is `'ask'` (default).
27
+ * When `humanInTheLoop.enabled` is true,
28
+ * the existing PreToolUse `'ask'` flow
29
+ * raises a tool_approval interrupt the
30
+ * host UI can render. When HITL is off,
31
+ * `'ask'` collapses to `deny` (matches
32
+ * the rest of the SDK's default).
33
+ *
34
+ * Default per-tool path extractors cover the local-engine coding
35
+ * suite (`read_file`, `write_file`, `edit_file`, `grep_search`,
36
+ * `glob_search`, `list_directory`, `compile_check`). The host can
37
+ * override or extend via `pathExtractors`. Bash/code paths are not
38
+ * extracted by default — bash command parsing is its own concern, and
39
+ * the existing `bashAst` validator + sandbox-runtime fs allowlist are
40
+ * the right gates for those.
41
+ *
42
+ * Important: this hook does NOT replace `resolveWorkspacePathSafe`.
43
+ * Even if the hook returns `allow`, the file tool still enforces its
44
+ * own clamp unless `workspace.allowReadOutside` /
45
+ * `workspace.allowWriteOutside` (or the legacy
46
+ * `allowOutsideWorkspace`) is set. The recommended composition for
47
+ * "ask the user" semantics is:
48
+ *
49
+ * workspace: {
50
+ * root,
51
+ * allowReadOutside: true,
52
+ * allowWriteOutside: true,
53
+ * },
54
+ * // …with the hook installed and humanInTheLoop.enabled = true.
55
+ */
56
+ const READ_TOOLS = new Set([
57
+ _enum.Constants.READ_FILE,
58
+ _enum.Constants.GREP_SEARCH,
59
+ _enum.Constants.GLOB_SEARCH,
60
+ _enum.Constants.LIST_DIRECTORY,
61
+ _enum.Constants.COMPILE_CHECK,
62
+ ]);
63
+ const WRITE_TOOLS = new Set([
64
+ _enum.Constants.WRITE_FILE,
65
+ _enum.Constants.EDIT_FILE,
66
+ ]);
67
+ /**
68
+ * Best-effort extractor for `compile_check` — pulls absolute and `~/`
69
+ * path tokens out of the `command` string so the workspace boundary
70
+ * sees them. Without this, a model could ship `command: 'cat
71
+ * /etc/passwd'` and the policy hook would short-circuit to `allow`
72
+ * (Codex P1 #26 — the prior `() => []` made the hook a no-op for
73
+ * compile_check). Conservative by design:
74
+ *
75
+ * - Matches `/foo`, `~/foo`, `$HOME/foo`, `${HOME}/foo` followed by
76
+ * non-shell-special chars. Stops at whitespace, quotes, redirect
77
+ * operators, pipes, semicolons.
78
+ * - Strips a leading `--flag=` so `--out=/etc/foo` extracts as
79
+ * `/etc/foo` (the path the agent's actually trying to write).
80
+ * - Misses relative paths (intended — those resolve under cwd
81
+ * anyway), and shell-substituted paths whose final form isn't
82
+ * visible at extract time. Hosts that need bulletproof gating
83
+ * should pair this with a `bash_tool`-level policy.
84
+ */
85
+ // `["']?` slots before AND after the captured path cover quoted
86
+ // forms like `cat "/etc/passwd"` and `--out='/tmp/x'`. Codex P1 #31
87
+ // — the previous regex only matched unquoted tokens, so a model
88
+ // could trivially bypass the workspace policy by quoting any
89
+ // destination path. The path content character class still excludes
90
+ // quotes/whitespace/shell-specials so we don't over-extract; that's
91
+ // the defensive trade we want for fallback-grep style matching.
92
+ //
93
+ // The `\.\.(?:\/[^…]*)?` alternation covers parent-traversal forms
94
+ // (`..`, `../secrets.txt`, `../foo/bar`). Without it, a model could
95
+ // exfiltrate parent-directory files via `cat ../secrets` and the
96
+ // hook would short-circuit to `allow` because the extractor saw no
97
+ // "absolute" token. The boundary check at the call site resolves
98
+ // non-absolute extracted tokens against `root`, so `../secrets`
99
+ // becomes `<parent-of-workspace>/secrets` which the boundary then
100
+ // correctly flags as outside. Codex P2 #35.
101
+ const PATH_TOKEN = /(?:^|[\s=])(?:--[^\s=]+=)?["']?(\/[^\s'"|;&<>()`]+|~\/[^\s'"|;&<>()`]+|\$\{?HOME\}?\/[^\s'"|;&<>()`]+|\.\.(?:\/[^\s'"|;&<>()`]*)?)["']?/g;
102
+ // Back-compat alias kept for any downstream import.
103
+ const ABSOLUTE_PATH_TOKEN = PATH_TOKEN;
104
+ function expandHomeRelative(token) {
105
+ // Expand ~/foo and $HOME/foo and ${HOME}/foo to absolute. The
106
+ // workspace boundary check resolves non-absolute paths against the
107
+ // workspace root, which would silently treat `~/secret` as
108
+ // `<workspace>/~/secret` — exactly the bypass the codex flagged.
109
+ const home = os.homedir();
110
+ if (token.startsWith('~/'))
111
+ return `${home}/${token.slice(2)}`;
112
+ if (token.startsWith('${HOME}/'))
113
+ return `${home}/${token.slice(8)}`;
114
+ if (token.startsWith('$HOME/'))
115
+ return `${home}/${token.slice(6)}`;
116
+ return token;
117
+ }
118
+ function extractCompileCheckPaths(input) {
119
+ const command = typeof input.command === 'string' ? input.command : '';
120
+ if (command === '')
121
+ return [];
122
+ const out = [];
123
+ for (const match of command.matchAll(ABSOLUTE_PATH_TOKEN)) {
124
+ out.push(expandHomeRelative(match[1]));
125
+ }
126
+ return out;
127
+ }
128
+ const DEFAULT_EXTRACTORS = {
129
+ [_enum.Constants.READ_FILE]: (i) => typeof i.file_path === 'string' ? [i.file_path] : [],
130
+ [_enum.Constants.WRITE_FILE]: (i) => typeof i.file_path === 'string' ? [i.file_path] : [],
131
+ [_enum.Constants.EDIT_FILE]: (i) => typeof i.file_path === 'string' ? [i.file_path] : [],
132
+ [_enum.Constants.GREP_SEARCH]: (i) => typeof i.path === 'string' && i.path !== '' ? [i.path] : [],
133
+ [_enum.Constants.GLOB_SEARCH]: (i) => typeof i.path === 'string' && i.path !== '' ? [i.path] : [],
134
+ [_enum.Constants.LIST_DIRECTORY]: (i) => typeof i.path === 'string' && i.path !== '' ? [i.path] : [],
135
+ [_enum.Constants.COMPILE_CHECK]: extractCompileCheckPaths,
136
+ };
137
+ function isInsideAnyRoot(absolutePath, roots) {
138
+ for (const root of roots) {
139
+ if (absolutePath === root)
140
+ return true;
141
+ const rel = path.relative(root, absolutePath);
142
+ if (!rel.startsWith('..') && !path.isAbsolute(rel))
143
+ return true;
144
+ }
145
+ return false;
146
+ }
147
+ /**
148
+ * Symlink-aware variant: realpaths the candidate AND the roots before
149
+ * comparing. Without this, a symlink inside the workspace pointing
150
+ * outside (e.g. `workspace/link → /etc/passwd`) compares as
151
+ * "in-workspace" lexically, but actually grants the agent reach
152
+ * outside the boundary. Critical when this hook is the primary gate
153
+ * (i.e. the host opted into `workspace.allowReadOutside: true` /
154
+ * `allowWriteOutside: true` so the file tools' own clamp is off).
155
+ *
156
+ * Handles paths that don't yet exist (e.g. `write_file` to a brand
157
+ * new path) by walking up to the nearest existing ancestor and
158
+ * realpathing that, then re-attaching the unresolved suffix. Mirrors
159
+ * `resolveWorkspacePathSafe`'s approach in LocalExecutionEngine.
160
+ */
161
+ async function realpathOrSelf(absolutePath) {
162
+ try {
163
+ return await promises.realpath(absolutePath);
164
+ }
165
+ catch {
166
+ return absolutePath;
167
+ }
168
+ }
169
+ async function realpathOfPathOrAncestor(absolutePath) {
170
+ let current = absolutePath;
171
+ let suffix = '';
172
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
173
+ while (true) {
174
+ try {
175
+ const real = await promises.realpath(current);
176
+ return suffix === '' ? real : path.resolve(real, suffix);
177
+ }
178
+ catch {
179
+ const parent = path.resolve(current, '..');
180
+ if (parent === current) {
181
+ return absolutePath;
182
+ }
183
+ const base = current.slice(parent.length + 1);
184
+ suffix = suffix === '' ? base : `${base}/${suffix}`;
185
+ current = parent;
186
+ }
187
+ }
188
+ }
189
+ async function isInsideAnyRootRealpath(absolutePath, realRoots) {
190
+ const real = await realpathOfPathOrAncestor(absolutePath);
191
+ return isInsideAnyRoot(real, [...realRoots]);
192
+ }
193
+ function formatReason(template, toolName, outsidePaths) {
194
+ const fallback = `Tool "${toolName}" wants to touch ${outsidePaths.length} path(s) outside the workspace: ${outsidePaths.join(', ')}`;
195
+ if (template == null)
196
+ return fallback;
197
+ return template
198
+ .replace(/\{tool\}/g, toolName)
199
+ .replace(/\{paths\}/g, outsidePaths.join(', '));
200
+ }
201
+ /**
202
+ * Build a `PreToolUse` callback that enforces the workspace policy.
203
+ * Register it on a `HookRegistry`:
204
+ *
205
+ * ```ts
206
+ * registry.register('PreToolUse', {
207
+ * hooks: [createWorkspacePolicyHook({ root, outsideWrite: 'ask' })],
208
+ * });
209
+ * ```
210
+ *
211
+ * The hook is composable with `createToolPolicyHook` — register both;
212
+ * `executeHooks` precedence (`deny > ask > allow`) sorts out which
213
+ * decision wins per call.
214
+ */
215
+ function createWorkspacePolicyHook(config) {
216
+ const root = path.resolve(config.root);
217
+ // Relative `additionalRoots` entries are anchored to `root` so a
218
+ // monorepo config like `additionalRoots: ['../shared']` resolves
219
+ // to a sibling of `root`, not of process.cwd. Matches
220
+ // `getWorkspaceRoots` in LocalExecutionEngine.
221
+ const additionalRoots = (config.additionalRoots ?? []).map((p) => path.isAbsolute(p) ? path.resolve(p) : path.resolve(root, p));
222
+ const allRoots = [root, ...additionalRoots];
223
+ // Pre-realpath the roots once at construction — these are stable
224
+ // per Run. The candidate paths get realpath'd lazily inside the
225
+ // hook callback. Cached so the per-call cost is just one realpath.
226
+ let realRootsPromise;
227
+ const getRealRoots = () => {
228
+ if (realRootsPromise == null) {
229
+ realRootsPromise = Promise.all(allRoots.map(realpathOrSelf));
230
+ }
231
+ return realRootsPromise;
232
+ };
233
+ const readPolicy = config.outsideRead ?? 'ask';
234
+ const writePolicy = config.outsideWrite ?? 'ask';
235
+ const extractors = {
236
+ ...DEFAULT_EXTRACTORS,
237
+ ...(config.pathExtractors ?? {}),
238
+ };
239
+ return async (input) => {
240
+ const extractor = extractors[input.toolName];
241
+ if (extractor == null)
242
+ return { decision: 'allow' };
243
+ const paths = extractor((input.toolInput ?? {}));
244
+ if (paths.length === 0)
245
+ return { decision: 'allow' };
246
+ // Two-stage check:
247
+ // 1. Lexical fast path — anything that's lexically inside the
248
+ // workspace AND doesn't get redirected by realpath stays
249
+ // allow-able without paying the realpath cost on every call.
250
+ // 2. For paths that look outside lexically OR look inside but
251
+ // may have been routed through a symlink, realpath both the
252
+ // candidate and the roots and compare. This catches the
253
+ // `workspace/link → /etc/passwd` escape that lexical-only
254
+ // checks miss.
255
+ const outside = [];
256
+ const realRoots = await getRealRoots();
257
+ for (const p of paths) {
258
+ const abs = path.isAbsolute(p) ? path.resolve(p) : path.resolve(root, p);
259
+ // Realpath is the source of truth — it catches both the
260
+ // symlink-escape case (lexically-inside path that resolves
261
+ // outside) and the alternate-mount case (lexically-outside
262
+ // path that resolves back inside the workspace). The lexical
263
+ // check alone gives the wrong answer for either, so we don't
264
+ // bother computing it.
265
+ const realInside = await isInsideAnyRootRealpath(abs, realRoots);
266
+ if (!realInside) {
267
+ outside.push(p);
268
+ }
269
+ }
270
+ if (outside.length === 0)
271
+ return { decision: 'allow' };
272
+ const policy = WRITE_TOOLS.has(input.toolName)
273
+ ? writePolicy
274
+ : READ_TOOLS.has(input.toolName)
275
+ ? readPolicy
276
+ : writePolicy; // unknown tools — treat as write (stricter)
277
+ if (policy === 'allow')
278
+ return { decision: 'allow' };
279
+ const decision = policy === 'deny' ? 'deny' : 'ask';
280
+ return {
281
+ decision,
282
+ reason: formatReason(config.reason, input.toolName, outside),
283
+ ...(decision === 'ask'
284
+ ? { allowedDecisions: ['approve', 'reject'] }
285
+ : {}),
286
+ };
287
+ };
288
+ }
289
+
290
+ exports.createWorkspacePolicyHook = createWorkspacePolicyHook;
291
+ //# sourceMappingURL=createWorkspacePolicyHook.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createWorkspacePolicyHook.cjs","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 { isAbsolute, relative, resolve } from 'path';\nimport { realpath } from 'fs/promises';\nimport { Constants } from '@/common';\nimport type {\n HookCallback,\n PreToolUseHookInput,\n PreToolUseHookOutput,\n ToolDecision,\n} from './types';\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(\n absolutePath: string\n): 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> = {\n ...DEFAULT_EXTRACTORS,\n ...(config.pathExtractors ?? {}),\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(\n (input.toolInput ?? {}) as Record<string, unknown>\n );\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 = WRITE_TOOLS.has(input.toolName)\n ? writePolicy\n : READ_TOOLS.has(input.toolName)\n ? readPolicy\n : writePolicy; // unknown tools — treat as write (stricter)\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"],"names":["Constants","homedir","relative","isAbsolute","realpath","resolve"],"mappings":";;;;;;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CG;AAiDH,MAAM,UAAU,GAAG,IAAI,GAAG,CAAS;AACjC,IAAAA,eAAS,CAAC,SAAS;AACnB,IAAAA,eAAS,CAAC,WAAW;AACrB,IAAAA,eAAS,CAAC,WAAW;AACrB,IAAAA,eAAS,CAAC,cAAc;AACxB,IAAAA,eAAS,CAAC,aAAa;AACxB,CAAA,CAAC;AAEF,MAAM,WAAW,GAAG,IAAI,GAAG,CAAS;AAClC,IAAAA,eAAS,CAAC,UAAU;AACpB,IAAAA,eAAS,CAAC,SAAS;AACpB,CAAA,CAAC;AAEF;;;;;;;;;;;;;;;;;AAiBG;AACH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM,UAAU,GACd,0IAA0I;AAC5I;AACA,MAAM,mBAAmB,GAAG,UAAU;AACtC,SAAS,kBAAkB,CAAC,KAAa,EAAA;;;;;AAKvC,IAAA,MAAM,IAAI,GAAGC,UAAO,EAAE;AACtB,IAAA,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA,CAAE;AAC9D,IAAA,IAAI,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA,CAAE;AACpE,IAAA,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA,CAAE;AAClE,IAAA,OAAO,KAAK;AACd;AACA,SAAS,wBAAwB,CAAC,KAA8B,EAAA;AAC9D,IAAA,MAAM,OAAO,GAAG,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,GAAG,KAAK,CAAC,OAAO,GAAG,EAAE;IACtE,IAAI,OAAO,KAAK,EAAE;AAAE,QAAA,OAAO,EAAE;IAC7B,MAAM,GAAG,GAAa,EAAE;IACxB,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE;QACzD,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC;AACA,IAAA,OAAO,GAAG;AACZ;AAEA,MAAM,kBAAkB,GAAkC;IACxD,CAACD,eAAS,CAAC,SAAS,GAAG,CAAC,CAAC,KACvB,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE;IACtD,CAACA,eAAS,CAAC,UAAU,GAAG,CAAC,CAAC,KACxB,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE;IACtD,CAACA,eAAS,CAAC,SAAS,GAAG,CAAC,CAAC,KACvB,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE;AACtD,IAAA,CAACA,eAAS,CAAC,WAAW,GAAG,CAAC,CAAC,KACzB,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;AAC7D,IAAA,CAACA,eAAS,CAAC,WAAW,GAAG,CAAC,CAAC,KACzB,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;AAC7D,IAAA,CAACA,eAAS,CAAC,cAAc,GAAG,CAAC,CAAC,KAC5B,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;AAC7D,IAAA,CAACA,eAAS,CAAC,aAAa,GAAG,wBAAwB;CACpD;AAED,SAAS,eAAe,CAAC,YAAoB,EAAE,KAAe,EAAA;AAC5D,IAAA,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;QACxB,IAAI,YAAY,KAAK,IAAI;AAAE,YAAA,OAAO,IAAI;QACtC,MAAM,GAAG,GAAGE,aAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;AACxC,QAAA,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAACC,eAAU,CAAC,GAAG,CAAC;AAAE,YAAA,OAAO,IAAI;IAC5D;AACA,IAAA,OAAO,KAAK;AACd;AAEA;;;;;;;;;;;;;AAaG;AACH,eAAe,cAAc,CAAC,YAAoB,EAAA;AAChD,IAAA,IAAI;AACF,QAAA,OAAO,MAAMC,iBAAQ,CAAC,YAAY,CAAC;IACrC;AAAE,IAAA,MAAM;AACN,QAAA,OAAO,YAAY;IACrB;AACF;AAEA,eAAe,wBAAwB,CACrC,YAAoB,EAAA;IAEpB,IAAI,OAAO,GAAG,YAAY;IAC1B,IAAI,MAAM,GAAG,EAAE;;IAEf,OAAO,IAAI,EAAE;AACX,QAAA,IAAI;AACF,YAAA,MAAM,IAAI,GAAG,MAAMA,iBAAQ,CAAC,OAAO,CAAC;AACpC,YAAA,OAAO,MAAM,KAAK,EAAE,GAAG,IAAI,GAAGC,YAAO,CAAC,IAAI,EAAE,MAAM,CAAC;QACrD;AAAE,QAAA,MAAM;YACN,MAAM,MAAM,GAAGA,YAAO,CAAC,OAAO,EAAE,IAAI,CAAC;AACrC,YAAA,IAAI,MAAM,KAAK,OAAO,EAAE;AACtB,gBAAA,OAAO,YAAY;YACrB;AACA,YAAA,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;AAC7C,YAAA,MAAM,GAAG,MAAM,KAAK,EAAE,GAAG,IAAI,GAAG,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,MAAM,EAAE;YACnD,OAAO,GAAG,MAAM;QAClB;IACF;AACF;AAEA,eAAe,uBAAuB,CACpC,YAAoB,EACpB,SAA4B,EAAA;AAE5B,IAAA,MAAM,IAAI,GAAG,MAAM,wBAAwB,CAAC,YAAY,CAAC;IACzD,OAAO,eAAe,CAAC,IAAI,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC;AAC9C;AAEA,SAAS,YAAY,CACnB,QAA4B,EAC5B,QAAgB,EAChB,YAA+B,EAAA;AAE/B,IAAA,MAAM,QAAQ,GAAG,CAAA,MAAA,EAAS,QAAQ,CAAA,iBAAA,EAAoB,YAAY,CAAC,MAAM,CAAA,gCAAA,EAAmC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;IACrI,IAAI,QAAQ,IAAI,IAAI;AAAE,QAAA,OAAO,QAAQ;AACrC,IAAA,OAAO;AACJ,SAAA,OAAO,CAAC,WAAW,EAAE,QAAQ;SAC7B,OAAO,CAAC,YAAY,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnD;AAEA;;;;;;;;;;;;;AAaG;AACG,SAAU,yBAAyB,CACvC,MAA6B,EAAA;IAE7B,MAAM,IAAI,GAAGA,YAAO,CAAC,MAAM,CAAC,IAAI,CAAC;;;;;AAKjC,IAAA,MAAM,eAAe,GAAG,CAAC,MAAM,CAAC,eAAe,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,KAC3DF,eAAU,CAAC,CAAC,CAAC,GAAGE,YAAO,CAAC,CAAC,CAAC,GAAGA,YAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAC9C;IACD,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,GAAG,eAAe,CAAC;;;;AAK3C,IAAA,IAAI,gBAA+C;IACnD,MAAM,YAAY,GAAG,MAAwB;AAC3C,QAAA,IAAI,gBAAgB,IAAI,IAAI,EAAE;AAC5B,YAAA,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC9D;AACA,QAAA,OAAO,gBAAgB;AACzB,IAAA,CAAC;AAED,IAAA,MAAM,UAAU,GAAwB,MAAM,CAAC,WAAW,IAAI,KAAK;AACnE,IAAA,MAAM,WAAW,GAAwB,MAAM,CAAC,YAAY,IAAI,KAAK;AAErE,IAAA,MAAM,UAAU,GAAkC;AAChD,QAAA,GAAG,kBAAkB;AACrB,QAAA,IAAI,MAAM,CAAC,cAAc,IAAI,EAAE,CAAC;KACjC;AAED,IAAA,OAAO,OAAO,KAA0B,KAAmC;QACzE,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC;QAC5C,IAAI,SAAS,IAAI,IAAI;AAAE,YAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE;AAEnD,QAAA,MAAM,KAAK,GAAG,SAAS,EACpB,KAAK,CAAC,SAAS,IAAI,EAAE,EACvB;AACD,QAAA,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;AAAE,YAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE;;;;;;;;;;QAWpD,MAAM,OAAO,GAAa,EAAE;AAC5B,QAAA,MAAM,SAAS,GAAG,MAAM,YAAY,EAAE;AACtC,QAAA,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE;YACrB,MAAM,GAAG,GAAGF,eAAU,CAAC,CAAC,CAAC,GAAGE,YAAO,CAAC,CAAC,CAAC,GAAGA,YAAO,CAAC,IAAI,EAAE,CAAC,CAAC;;;;;;;YAOzD,MAAM,UAAU,GAAG,MAAM,uBAAuB,CAAC,GAAG,EAAE,SAAS,CAAC;YAChE,IAAI,CAAC,UAAU,EAAE;AACf,gBAAA,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YACjB;QACF;AACA,QAAA,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;AAAE,YAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE;QAEtD,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ;AAC3C,cAAE;cACA,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ;AAC7B,kBAAE;AACF,kBAAE,WAAW,CAAC;QAClB,IAAI,MAAM,KAAK,OAAO;AAAE,YAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE;AAEpD,QAAA,MAAM,QAAQ,GAAiB,MAAM,KAAK,MAAM,GAAG,MAAM,GAAG,KAAK;QACjE,OAAO;YACL,QAAQ;AACR,YAAA,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC;YAC5D,IAAI,QAAQ,KAAK;kBACb,EAAE,gBAAgB,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAU;kBAClD,EAAE,CAAC;SACR;AACH,IAAA,CAAC;AACH;;;;"}
package/dist/cjs/main.cjs CHANGED
@@ -9,11 +9,13 @@ var ids = require('./messages/ids.cjs');
9
9
  var prune = require('./messages/prune.cjs');
10
10
  var format = require('./messages/format.cjs');
11
11
  var cache = require('./messages/cache.cjs');
12
+ var anthropicToolCache = require('./messages/anthropicToolCache.cjs');
12
13
  var content = require('./messages/content.cjs');
13
14
  var tools$1 = require('./messages/tools.cjs');
14
15
  var contextPruning = require('./messages/contextPruning.cjs');
15
16
  var contextPruningSettings = require('./messages/contextPruningSettings.cjs');
16
17
  var reducer = require('./messages/reducer.cjs');
18
+ var recency = require('./messages/recency.cjs');
17
19
  var Graph = require('./graphs/Graph.cjs');
18
20
  var MultiAgentGraph = require('./graphs/MultiAgentGraph.cjs');
19
21
  var index$2 = require('./summarization/index.cjs');
@@ -31,6 +33,18 @@ var ToolSearch = require('./tools/ToolSearch.cjs');
31
33
  var ToolNode = require('./tools/ToolNode.cjs');
32
34
  var schema$1 = require('./tools/schema.cjs');
33
35
  var handlers$1 = require('./tools/handlers.cjs');
36
+ var CompileCheckTool = require('./tools/local/CompileCheckTool.cjs');
37
+ var FileCheckpointer = require('./tools/local/FileCheckpointer.cjs');
38
+ var LocalCodingTools = require('./tools/local/LocalCodingTools.cjs');
39
+ var LocalExecutionEngine = require('./tools/local/LocalExecutionEngine.cjs');
40
+ var LocalExecutionTools = require('./tools/local/LocalExecutionTools.cjs');
41
+ var LocalProgrammaticToolCalling = require('./tools/local/LocalProgrammaticToolCalling.cjs');
42
+ var resolveLocalExecutionTools = require('./tools/local/resolveLocalExecutionTools.cjs');
43
+ var attachments = require('./tools/local/attachments.cjs');
44
+ var bashAst = require('./tools/local/bashAst.cjs');
45
+ var editStrategies = require('./tools/local/editStrategies.cjs');
46
+ var syntaxCheck = require('./tools/local/syntaxCheck.cjs');
47
+ var textEncoding = require('./tools/local/textEncoding.cjs');
34
48
  var tool = require('./tools/search/tool.cjs');
35
49
  var schema = require('./tools/search/schema.cjs');
36
50
  var constants = require('./common/constants.cjs');
@@ -48,6 +62,7 @@ var HookRegistry = require('./hooks/HookRegistry.cjs');
48
62
  var executeHooks = require('./hooks/executeHooks.cjs');
49
63
  var matchers = require('./hooks/matchers.cjs');
50
64
  var createToolPolicyHook = require('./hooks/createToolPolicyHook.cjs');
65
+ var createWorkspacePolicyHook = require('./hooks/createWorkspacePolicyHook.cjs');
51
66
  var types = require('./hooks/types.cjs');
52
67
  var askUserQuestion = require('./hitl/askUserQuestion.cjs');
53
68
  var messages = require('@langchain/core/messages');
@@ -87,9 +102,11 @@ exports.getConverseOverrideMessage = core.getConverseOverrideMessage;
87
102
  exports.modifyDeltaProperties = core.modifyDeltaProperties;
88
103
  exports.getMessageId = ids.getMessageId;
89
104
  exports.DEFAULT_RESERVE_RATIO = prune.DEFAULT_RESERVE_RATIO;
105
+ exports.ORIGINAL_CONTENT_MAX_CHARS = prune.ORIGINAL_CONTENT_MAX_CHARS;
90
106
  exports.calculateTotalTokens = prune.calculateTotalTokens;
91
107
  exports.checkValidNumber = prune.checkValidNumber;
92
108
  exports.createPruneMessages = prune.createPruneMessages;
109
+ exports.enforceOriginalContentCap = prune.enforceOriginalContentCap;
93
110
  exports.getMessagesWithinTokenLimit = prune.getMessagesWithinTokenLimit;
94
111
  exports.maskConsumedToolResults = prune.maskConsumedToolResults;
95
112
  exports.preFlightTruncateToolCallInputs = prune.preFlightTruncateToolCallInputs;
@@ -108,6 +125,8 @@ exports.addBedrockCacheControl = cache.addBedrockCacheControl;
108
125
  exports.addCacheControl = cache.addCacheControl;
109
126
  exports.stripAnthropicCacheControl = cache.stripAnthropicCacheControl;
110
127
  exports.stripBedrockCacheControl = cache.stripBedrockCacheControl;
128
+ exports.makeIsDeferred = anthropicToolCache.makeIsDeferred;
129
+ exports.partitionAndMarkAnthropicToolCache = anthropicToolCache.partitionAndMarkAnthropicToolCache;
111
130
  exports.formatContentStrings = content.formatContentStrings;
112
131
  exports.extractToolDiscoveries = tools$1.extractToolDiscoveries;
113
132
  exports.hasToolSearchInCurrentTurn = tools$1.hasToolSearchInCurrentTurn;
@@ -117,6 +136,7 @@ exports.resolveContextPruningSettings = contextPruningSettings.resolveContextPru
117
136
  exports.REMOVE_ALL_MESSAGES = reducer.REMOVE_ALL_MESSAGES;
118
137
  exports.createRemoveAllMessage = reducer.createRemoveAllMessage;
119
138
  exports.messagesStateReducer = reducer.messagesStateReducer;
139
+ exports.splitAtRecencyBoundary = recency.splitAtRecencyBoundary;
120
140
  exports.Graph = Graph.Graph;
121
141
  exports.StandardGraph = Graph.StandardGraph;
122
142
  exports.MultiAgentGraph = MultiAgentGraph.MultiAgentGraph;
@@ -210,6 +230,73 @@ exports.handleServerToolResult = handlers$1.handleServerToolResult;
210
230
  exports.handleToolCallChunks = handlers$1.handleToolCallChunks;
211
231
  exports.handleToolCalls = handlers$1.handleToolCalls;
212
232
  exports.toolResultTypes = handlers$1.toolResultTypes;
233
+ exports.CompileCheckToolName = CompileCheckTool.CompileCheckToolName;
234
+ exports.createCompileCheckTool = CompileCheckTool.createCompileCheckTool;
235
+ exports.createCompileCheckToolDefinition = CompileCheckTool.createCompileCheckToolDefinition;
236
+ exports.LocalFileCheckpointerImpl = FileCheckpointer.LocalFileCheckpointerImpl;
237
+ exports.createLocalFileCheckpointer = FileCheckpointer.createLocalFileCheckpointer;
238
+ exports.LocalEditFileToolName = LocalCodingTools.LocalEditFileToolName;
239
+ exports.LocalEditFileToolSchema = LocalCodingTools.LocalEditFileToolSchema;
240
+ exports.LocalGlobSearchToolName = LocalCodingTools.LocalGlobSearchToolName;
241
+ exports.LocalGlobSearchToolSchema = LocalCodingTools.LocalGlobSearchToolSchema;
242
+ exports.LocalGrepSearchToolName = LocalCodingTools.LocalGrepSearchToolName;
243
+ exports.LocalGrepSearchToolSchema = LocalCodingTools.LocalGrepSearchToolSchema;
244
+ exports.LocalListDirectoryToolName = LocalCodingTools.LocalListDirectoryToolName;
245
+ exports.LocalListDirectoryToolSchema = LocalCodingTools.LocalListDirectoryToolSchema;
246
+ exports.LocalReadFileToolSchema = LocalCodingTools.LocalReadFileToolSchema;
247
+ exports.LocalWriteFileToolName = LocalCodingTools.LocalWriteFileToolName;
248
+ exports.LocalWriteFileToolSchema = LocalCodingTools.LocalWriteFileToolSchema;
249
+ exports._resetRipgrepCacheForTests = LocalCodingTools._resetRipgrepCacheForTests;
250
+ exports.createLocalCodingToolBundle = LocalCodingTools.createLocalCodingToolBundle;
251
+ exports.createLocalCodingToolDefinitions = LocalCodingTools.createLocalCodingToolDefinitions;
252
+ exports.createLocalCodingToolRegistry = LocalCodingTools.createLocalCodingToolRegistry;
253
+ exports.createLocalCodingTools = LocalCodingTools.createLocalCodingTools;
254
+ exports.createLocalEditFileTool = LocalCodingTools.createLocalEditFileTool;
255
+ exports.createLocalGlobSearchTool = LocalCodingTools.createLocalGlobSearchTool;
256
+ exports.createLocalGrepSearchTool = LocalCodingTools.createLocalGrepSearchTool;
257
+ exports.createLocalListDirectoryTool = LocalCodingTools.createLocalListDirectoryTool;
258
+ exports.createLocalReadFileTool = LocalCodingTools.createLocalReadFileTool;
259
+ exports.createLocalWriteFileTool = LocalCodingTools.createLocalWriteFileTool;
260
+ exports._resetLocalEngineWarningsForTests = LocalExecutionEngine._resetLocalEngineWarningsForTests;
261
+ exports.buildSandboxRuntimeConfig = LocalExecutionEngine.buildSandboxRuntimeConfig;
262
+ exports.executeLocalBash = LocalExecutionEngine.executeLocalBash;
263
+ exports.executeLocalBashWithArgs = LocalExecutionEngine.executeLocalBashWithArgs;
264
+ exports.executeLocalCode = LocalExecutionEngine.executeLocalCode;
265
+ exports.getLocalCwd = LocalExecutionEngine.getLocalCwd;
266
+ exports.getLocalSessionId = LocalExecutionEngine.getLocalSessionId;
267
+ exports.getReadRoots = LocalExecutionEngine.getReadRoots;
268
+ exports.getSpawn = LocalExecutionEngine.getSpawn;
269
+ exports.getWorkspaceFS = LocalExecutionEngine.getWorkspaceFS;
270
+ exports.getWorkspaceRoots = LocalExecutionEngine.getWorkspaceRoots;
271
+ exports.getWriteRoots = LocalExecutionEngine.getWriteRoots;
272
+ exports.resolveLocalExecutionConfig = LocalExecutionEngine.resolveLocalExecutionConfig;
273
+ exports.resolveWorkspacePath = LocalExecutionEngine.resolveWorkspacePath;
274
+ exports.resolveWorkspacePathSafe = LocalExecutionEngine.resolveWorkspacePathSafe;
275
+ exports.shellQuote = LocalExecutionEngine.shellQuote;
276
+ exports.spawnLocalProcess = LocalExecutionEngine.spawnLocalProcess;
277
+ exports.truncateLocalOutput = LocalExecutionEngine.truncateLocalOutput;
278
+ exports.validateBashCommand = LocalExecutionEngine.validateBashCommand;
279
+ exports.LocalBashExecutionToolDescription = LocalExecutionTools.LocalBashExecutionToolDescription;
280
+ exports.LocalCodeExecutionToolDescription = LocalExecutionTools.LocalCodeExecutionToolDescription;
281
+ exports.createLocalBashExecutionTool = LocalExecutionTools.createLocalBashExecutionTool;
282
+ exports.createLocalCodeExecutionTool = LocalExecutionTools.createLocalCodeExecutionTool;
283
+ exports._createBashProgramForTests = LocalProgrammaticToolCalling._createBashProgramForTests;
284
+ exports.applyPreToolUseHooksForBridge = LocalProgrammaticToolCalling.applyPreToolUseHooksForBridge;
285
+ exports.createLocalBashProgrammaticToolCallingTool = LocalProgrammaticToolCalling.createLocalBashProgrammaticToolCallingTool;
286
+ exports.createLocalProgrammaticToolCallingTool = LocalProgrammaticToolCalling.createLocalProgrammaticToolCallingTool;
287
+ exports.resolveLocalExecutionTools = resolveLocalExecutionTools.resolveLocalExecutionTools;
288
+ exports.resolveLocalToolRegistry = resolveLocalExecutionTools.resolveLocalToolRegistry;
289
+ exports.resolveLocalToolsForBinding = resolveLocalExecutionTools.resolveLocalToolsForBinding;
290
+ exports.classifyAttachment = attachments.classifyAttachment;
291
+ exports.imageAttachmentContent = attachments.imageAttachmentContent;
292
+ exports.bashAstFindingsToErrors = bashAst.bashAstFindingsToErrors;
293
+ exports.runBashAstChecks = bashAst.runBashAstChecks;
294
+ exports.applyEdit = editStrategies.applyEdit;
295
+ exports.locateEdit = editStrategies.locateEdit;
296
+ exports._resetSyntaxCheckProbeCacheForTests = syntaxCheck._resetSyntaxCheckProbeCacheForTests;
297
+ exports.runPostEditSyntaxCheck = syntaxCheck.runPostEditSyntaxCheck;
298
+ exports.decodeFile = textEncoding.decodeFile;
299
+ exports.encodeFile = textEncoding.encodeFile;
213
300
  exports.createSearchTool = tool.createSearchTool;
214
301
  Object.defineProperty(exports, "DATE_RANGE", {
215
302
  enumerable: true,
@@ -262,6 +349,8 @@ Object.defineProperty(exports, "GraphNodeKeys", {
262
349
  enumerable: true,
263
350
  get: function () { return _enum.GraphNodeKeys; }
264
351
  });
352
+ exports.LOCAL_CODING_BUNDLE_NAMES = _enum.LOCAL_CODING_BUNDLE_NAMES;
353
+ exports.LOCAL_CODING_TOOL_NAMES = _enum.LOCAL_CODING_TOOL_NAMES;
265
354
  Object.defineProperty(exports, "Providers", {
266
355
  enumerable: true,
267
356
  get: function () { return _enum.Providers; }
@@ -314,6 +403,7 @@ exports.MAX_PATTERN_LENGTH = matchers.MAX_PATTERN_LENGTH;
314
403
  exports.hasNestedQuantifier = matchers.hasNestedQuantifier;
315
404
  exports.matchesQuery = matchers.matchesQuery;
316
405
  exports.createToolPolicyHook = createToolPolicyHook.createToolPolicyHook;
406
+ exports.createWorkspacePolicyHook = createWorkspacePolicyHook.createWorkspacePolicyHook;
317
407
  exports.HOOK_EVENTS = types.HOOK_EVENTS;
318
408
  exports.askUserQuestion = askUserQuestion.askUserQuestion;
319
409
  Object.defineProperty(exports, "AIMessage", {
@@ -1 +1 @@
1
- {"version":3,"file":"main.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"main.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Anthropic prompt-caching helper for the `tools[]` request field.
5
+ *
6
+ * Anthropic accepts `cache_control: { type: 'ephemeral' }` on individual
7
+ * tool definitions. Whichever tool carries the marker becomes the end of
8
+ * a cached prefix: `tools[0..N]` (everything up to and including the
9
+ * marked tool) is cached and rebated on subsequent matching requests.
10
+ *
11
+ * For agents that mix static and deferred (lazily-discovered) tools, the
12
+ * winning configuration is:
13
+ *
14
+ * 1. Stable-partition tools so all *static* (non-deferred) tools come
15
+ * first and discovered-deferred tools come last.
16
+ * 2. Stamp `cache_control` on the LAST static tool.
17
+ *
18
+ * That way, the cached prefix covers exactly the static tool inventory.
19
+ * Discovered tools that show up later (or vary turn-to-turn as new ones
20
+ * get discovered) never invalidate the prefix because they sit *after*
21
+ * the breakpoint.
22
+ *
23
+ * LangChain's Anthropic adapter passes the marker through via
24
+ * `tool.extras.cache_control` (`AnthropicToolExtrasSchema`), so we set
25
+ * it as an `extras` field on a fresh wrapper around the tool — never
26
+ * mutating the original tool instance, since callers may share them
27
+ * across runs.
28
+ */
29
+ /**
30
+ * Returns a callable that reports whether a given tool *name* is deferred
31
+ * according to the supplied registry of tool definitions. Tools without
32
+ * a registry entry are treated as non-deferred (i.e. static), matching
33
+ * the host-supplied `graphTools` semantics elsewhere in the SDK.
34
+ */
35
+ function makeIsDeferred(toolDefinitions) {
36
+ if (toolDefinitions == null || toolDefinitions.length === 0) {
37
+ return () => false;
38
+ }
39
+ const deferred = new Set();
40
+ for (const def of toolDefinitions) {
41
+ if (def.defer_loading === true)
42
+ deferred.add(def.name);
43
+ }
44
+ if (deferred.size === 0)
45
+ return () => false;
46
+ return (name) => deferred.has(name);
47
+ }
48
+ /**
49
+ * Stable-partition `tools` into [static..., deferred...] and stamp the
50
+ * Anthropic `cache_control: ephemeral` marker on the last static tool.
51
+ *
52
+ * If `tools` is undefined or empty, or no entry has a usable `.name`,
53
+ * returns the input unchanged. If there are no static tools at all,
54
+ * also returns unchanged (nothing to cache).
55
+ *
56
+ * The original tool instances are never mutated. The marked entry is a
57
+ * shallow wrapper that preserves the prototype chain so downstream
58
+ * `instanceof` checks still pass. `extras` is merged so any existing
59
+ * `providerToolDefinition` / other extras the host attached are kept.
60
+ */
61
+ function partitionAndMarkAnthropicToolCache(tools, isDeferred) {
62
+ if (tools == null || tools.length === 0)
63
+ return tools;
64
+ // Use unknown[] internally to avoid GraphTools' union-array variance
65
+ // (each member is one of three array types). We cast back to
66
+ // GraphTools when returning.
67
+ const staticTools = [];
68
+ const deferredTools = [];
69
+ for (const tool of tools) {
70
+ const name = tool.name;
71
+ if (typeof name === 'string' && isDeferred(name)) {
72
+ deferredTools.push(tool);
73
+ }
74
+ else {
75
+ staticTools.push(tool);
76
+ }
77
+ }
78
+ if (staticTools.length === 0) {
79
+ return tools;
80
+ }
81
+ const last = staticTools[staticTools.length - 1];
82
+ // Already marked? Don't double-clone.
83
+ if (last.extras != null &&
84
+ 'cache_control' in last.extras &&
85
+ last.extras.cache_control != null) {
86
+ if (deferredTools.length === 0)
87
+ return tools;
88
+ return [...staticTools, ...deferredTools];
89
+ }
90
+ const wrapped = Object.assign(Object.create(Object.getPrototypeOf(last) ?? Object.prototype), last, {
91
+ extras: {
92
+ ...(last.extras ?? {}),
93
+ cache_control: { type: 'ephemeral' },
94
+ },
95
+ });
96
+ staticTools[staticTools.length - 1] = wrapped;
97
+ return [...staticTools, ...deferredTools];
98
+ }
99
+
100
+ exports.makeIsDeferred = makeIsDeferred;
101
+ exports.partitionAndMarkAnthropicToolCache = partitionAndMarkAnthropicToolCache;
102
+ //# sourceMappingURL=anthropicToolCache.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropicToolCache.cjs","sources":["../../../src/messages/anthropicToolCache.ts"],"sourcesContent":["/**\n * Anthropic prompt-caching helper for the `tools[]` request field.\n *\n * Anthropic accepts `cache_control: { type: 'ephemeral' }` on individual\n * tool definitions. Whichever tool carries the marker becomes the end of\n * a cached prefix: `tools[0..N]` (everything up to and including the\n * marked tool) is cached and rebated on subsequent matching requests.\n *\n * For agents that mix static and deferred (lazily-discovered) tools, the\n * winning configuration is:\n *\n * 1. Stable-partition tools so all *static* (non-deferred) tools come\n * first and discovered-deferred tools come last.\n * 2. Stamp `cache_control` on the LAST static tool.\n *\n * That way, the cached prefix covers exactly the static tool inventory.\n * Discovered tools that show up later (or vary turn-to-turn as new ones\n * get discovered) never invalidate the prefix because they sit *after*\n * the breakpoint.\n *\n * LangChain's Anthropic adapter passes the marker through via\n * `tool.extras.cache_control` (`AnthropicToolExtrasSchema`), so we set\n * it as an `extras` field on a fresh wrapper around the tool — never\n * mutating the original tool instance, since callers may share them\n * across runs.\n */\n\nimport type { GraphTools } from '@/types';\n\n/**\n * Returns a callable that reports whether a given tool *name* is deferred\n * according to the supplied registry of tool definitions. Tools without\n * a registry entry are treated as non-deferred (i.e. static), matching\n * the host-supplied `graphTools` semantics elsewhere in the SDK.\n */\nexport function makeIsDeferred(\n toolDefinitions:\n | ReadonlyArray<{ name: string; defer_loading?: boolean }>\n | undefined\n): (toolName: string) => boolean {\n if (toolDefinitions == null || toolDefinitions.length === 0) {\n return () => false;\n }\n const deferred = new Set<string>();\n for (const def of toolDefinitions) {\n if (def.defer_loading === true) deferred.add(def.name);\n }\n if (deferred.size === 0) return () => false;\n return (name) => deferred.has(name);\n}\n\n/**\n * Stable-partition `tools` into [static..., deferred...] and stamp the\n * Anthropic `cache_control: ephemeral` marker on the last static tool.\n *\n * If `tools` is undefined or empty, or no entry has a usable `.name`,\n * returns the input unchanged. If there are no static tools at all,\n * also returns unchanged (nothing to cache).\n *\n * The original tool instances are never mutated. The marked entry is a\n * shallow wrapper that preserves the prototype chain so downstream\n * `instanceof` checks still pass. `extras` is merged so any existing\n * `providerToolDefinition` / other extras the host attached are kept.\n */\nexport function partitionAndMarkAnthropicToolCache(\n tools: GraphTools | undefined,\n isDeferred: (toolName: string) => boolean\n): GraphTools | undefined {\n if (tools == null || tools.length === 0) return tools;\n\n // Use unknown[] internally to avoid GraphTools' union-array variance\n // (each member is one of three array types). We cast back to\n // GraphTools when returning.\n const staticTools: unknown[] = [];\n const deferredTools: unknown[] = [];\n\n for (const tool of tools) {\n const name = (tool as { name?: unknown }).name;\n if (typeof name === 'string' && isDeferred(name)) {\n deferredTools.push(tool);\n } else {\n staticTools.push(tool);\n }\n }\n\n if (staticTools.length === 0) {\n return tools;\n }\n\n const last = staticTools[staticTools.length - 1] as {\n extras?: Record<string, unknown>;\n };\n // Already marked? Don't double-clone.\n if (\n last.extras != null &&\n 'cache_control' in last.extras &&\n (last.extras as { cache_control?: unknown }).cache_control != null\n ) {\n if (deferredTools.length === 0) return tools;\n return [...staticTools, ...deferredTools] as GraphTools;\n }\n\n const wrapped = Object.assign(\n Object.create(Object.getPrototypeOf(last) ?? Object.prototype),\n last,\n {\n extras: {\n ...((last.extras as Record<string, unknown> | undefined) ?? {}),\n cache_control: { type: 'ephemeral' as const },\n },\n }\n );\n\n staticTools[staticTools.length - 1] = wrapped;\n return [...staticTools, ...deferredTools] as GraphTools;\n}\n"],"names":[],"mappings":";;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;AAIH;;;;;AAKG;AACG,SAAU,cAAc,CAC5B,eAEa,EAAA;IAEb,IAAI,eAAe,IAAI,IAAI,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE;AAC3D,QAAA,OAAO,MAAM,KAAK;IACpB;AACA,IAAA,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU;AAClC,IAAA,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE;AACjC,QAAA,IAAI,GAAG,CAAC,aAAa,KAAK,IAAI;AAAE,YAAA,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;IACxD;AACA,IAAA,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC;AAAE,QAAA,OAAO,MAAM,KAAK;IAC3C,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;AACrC;AAEA;;;;;;;;;;;;AAYG;AACG,SAAU,kCAAkC,CAChD,KAA6B,EAC7B,UAAyC,EAAA;IAEzC,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;AAAE,QAAA,OAAO,KAAK;;;;IAKrD,MAAM,WAAW,GAAc,EAAE;IACjC,MAAM,aAAa,GAAc,EAAE;AAEnC,IAAA,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;AACxB,QAAA,MAAM,IAAI,GAAI,IAA2B,CAAC,IAAI;QAC9C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE;AAChD,YAAA,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;QAC1B;aAAO;AACL,YAAA,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;QACxB;IACF;AAEA,IAAA,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE;AAC5B,QAAA,OAAO,KAAK;IACd;IAEA,MAAM,IAAI,GAAG,WAAW,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAE9C;;AAED,IAAA,IACE,IAAI,CAAC,MAAM,IAAI,IAAI;QACnB,eAAe,IAAI,IAAI,CAAC,MAAM;AAC7B,QAAA,IAAI,CAAC,MAAsC,CAAC,aAAa,IAAI,IAAI,EAClE;AACA,QAAA,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;AAAE,YAAA,OAAO,KAAK;AAC5C,QAAA,OAAO,CAAC,GAAG,WAAW,EAAE,GAAG,aAAa,CAAe;IACzD;IAEA,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAC3B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,EAC9D,IAAI,EACJ;AACE,QAAA,MAAM,EAAE;AACN,YAAA,IAAK,IAAI,CAAC,MAA8C,IAAI,EAAE,CAAC;AAC/D,YAAA,aAAa,EAAE,EAAE,IAAI,EAAE,WAAoB,EAAE;AAC9C,SAAA;AACF,KAAA,CACF;IAED,WAAW,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,OAAO;AAC7C,IAAA,OAAO,CAAC,GAAG,WAAW,EAAE,GAAG,aAAa,CAAe;AACzD;;;;;"}