@librechat/agents 3.1.78 → 3.1.80-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/cjs/llm/anthropic/index.cjs +44 -55
  2. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  3. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +33 -21
  4. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  5. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +0 -4
  6. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
  7. package/dist/cjs/messages/anthropicToolCache.cjs +48 -15
  8. package/dist/cjs/messages/anthropicToolCache.cjs.map +1 -1
  9. package/dist/cjs/messages/format.cjs +97 -14
  10. package/dist/cjs/messages/format.cjs.map +1 -1
  11. package/dist/cjs/tools/BashExecutor.cjs +10 -2
  12. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  13. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -1
  14. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  15. package/dist/cjs/tools/CodeExecutor.cjs +16 -5
  16. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  17. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +9 -4
  18. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  19. package/dist/cjs/tools/ToolNode.cjs +63 -40
  20. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  21. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +14 -16
  22. package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -1
  23. package/dist/cjs/tools/local/LocalExecutionTools.cjs.map +1 -1
  24. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
  25. package/dist/esm/llm/anthropic/index.mjs +43 -54
  26. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  27. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +33 -21
  28. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  29. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +0 -4
  30. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
  31. package/dist/esm/messages/anthropicToolCache.mjs +48 -15
  32. package/dist/esm/messages/anthropicToolCache.mjs.map +1 -1
  33. package/dist/esm/messages/format.mjs +97 -14
  34. package/dist/esm/messages/format.mjs.map +1 -1
  35. package/dist/esm/tools/BashExecutor.mjs +10 -2
  36. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  37. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +2 -1
  38. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  39. package/dist/esm/tools/CodeExecutor.mjs +16 -5
  40. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  41. package/dist/esm/tools/ProgrammaticToolCalling.mjs +9 -4
  42. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  43. package/dist/esm/tools/ToolNode.mjs +63 -40
  44. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  45. package/dist/esm/tools/local/LocalExecutionEngine.mjs +14 -16
  46. package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -1
  47. package/dist/esm/tools/local/LocalExecutionTools.mjs.map +1 -1
  48. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
  49. package/dist/types/llm/anthropic/index.d.ts +1 -9
  50. package/dist/types/messages/anthropicToolCache.d.ts +5 -5
  51. package/dist/types/types/tools.d.ts +82 -17
  52. package/package.json +1 -1
  53. package/src/llm/anthropic/index.ts +55 -64
  54. package/src/llm/anthropic/llm.spec.ts +585 -0
  55. package/src/llm/anthropic/utils/message_inputs.ts +36 -21
  56. package/src/llm/anthropic/utils/message_outputs.ts +0 -4
  57. package/src/llm/anthropic/utils/server-tool-inputs.test.ts +95 -13
  58. package/src/messages/__tests__/anthropicToolCache.test.ts +46 -0
  59. package/src/messages/anthropicToolCache.ts +70 -25
  60. package/src/messages/format.ts +117 -18
  61. package/src/messages/formatAgentMessages.test.ts +202 -1
  62. package/src/scripts/code_exec_multi_session.ts +4 -4
  63. package/src/specs/summarization.test.ts +3 -3
  64. package/src/tools/BashExecutor.ts +11 -3
  65. package/src/tools/BashProgrammaticToolCalling.ts +6 -6
  66. package/src/tools/CodeExecutor.ts +17 -6
  67. package/src/tools/ProgrammaticToolCalling.ts +14 -10
  68. package/src/tools/ToolNode.ts +85 -48
  69. package/src/tools/__tests__/LocalExecutionRoots.test.ts +8 -0
  70. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +9 -2
  71. package/src/tools/__tests__/ToolNode.session.test.ts +131 -50
  72. package/src/tools/local/LocalExecutionEngine.ts +55 -54
  73. package/src/tools/local/LocalExecutionTools.ts +2 -2
  74. package/src/tools/local/LocalProgrammaticToolCalling.ts +23 -6
  75. package/src/types/diff.d.ts +15 -0
  76. package/src/types/tools.ts +79 -17
@@ -5,7 +5,6 @@ import { mkdir, realpath, rm, writeFile } from 'fs/promises';
5
5
  import { createWriteStream } from 'fs';
6
6
  import { spawn } from 'child_process';
7
7
  import type { ChildProcess } from 'child_process';
8
- import type { SandboxRuntimeConfig } from '@anthropic-ai/sandbox-runtime';
9
8
  import { runBashAstChecks, bashAstFindingsToErrors } from './bashAst';
10
9
  import { nodeWorkspaceFS } from './workspaceFS';
11
10
  import type { WorkspaceFS } from './workspaceFS';
@@ -188,8 +187,17 @@ type RuntimeCommand = {
188
187
  source?: string;
189
188
  };
190
189
 
191
- type SandboxRuntimeModule = typeof import('@anthropic-ai/sandbox-runtime');
192
- type SandboxManagerType = SandboxRuntimeModule['SandboxManager'];
190
+ type SandboxManagerType = {
191
+ checkDependencies(): { errors: string[] };
192
+ initialize(config: BuiltSandboxRuntimeConfig): Promise<void>;
193
+ reset(): Promise<void>;
194
+ wrapWithSandbox(command: string): Promise<string>;
195
+ };
196
+
197
+ type SandboxRuntimeModule = {
198
+ getDefaultWritePaths(): string[];
199
+ SandboxManager: SandboxManagerType;
200
+ };
193
201
 
194
202
  let sandboxConfigKey: string | undefined;
195
203
  let sandboxInitialized = false;
@@ -229,9 +237,7 @@ export function getLocalCwd(config?: t.LocalExecutionConfig): string {
229
237
  * Returns plain absolute paths — callers symlink-resolve when they
230
238
  * need realpath equality (see `resolveWorkspacePathSafe`).
231
239
  */
232
- export function getWorkspaceRoots(
233
- config?: t.LocalExecutionConfig
234
- ): string[] {
240
+ export function getWorkspaceRoots(config?: t.LocalExecutionConfig): string[] {
235
241
  const root = getLocalCwd(config);
236
242
  const extras = config?.workspace?.additionalRoots ?? [];
237
243
  if (extras.length === 0) return [root];
@@ -258,9 +264,7 @@ export function getWorkspaceRoots(
258
264
  * back to the legacy top-level `local.spawn`, then to Node's
259
265
  * `child_process.spawn`. Centralised so engine swapping is one knob.
260
266
  */
261
- export function getSpawn(
262
- config?: t.LocalExecutionConfig
263
- ): t.LocalSpawn {
267
+ export function getSpawn(config?: t.LocalExecutionConfig): t.LocalSpawn {
264
268
  return (config?.exec?.spawn ?? config?.spawn ?? spawn) as t.LocalSpawn;
265
269
  }
266
270
 
@@ -269,9 +273,7 @@ export function getSpawn(
269
273
  * to the Node-host implementation. A future remote engine supplies
270
274
  * its own implementation here and inherits every file-touching tool.
271
275
  */
272
- export function getWorkspaceFS(
273
- config?: t.LocalExecutionConfig
274
- ): WorkspaceFS {
276
+ export function getWorkspaceFS(config?: t.LocalExecutionConfig): WorkspaceFS {
275
277
  return config?.exec?.fs ?? nodeWorkspaceFS;
276
278
  }
277
279
 
@@ -282,17 +284,17 @@ export function getWorkspaceFS(
282
284
  * helpers interpret as "skip the write clamp".
283
285
  */
284
286
  export function getWriteRoots(
285
- config?: t.LocalExecutionConfig
287
+ config: t.LocalExecutionConfig = {}
286
288
  ): string[] | null {
287
289
  // Granular flag wins over the legacy one when explicitly set
288
290
  // (true OR false) — otherwise a host tightening access during
289
291
  // migration (`allowOutsideWorkspace: true, workspace.
290
292
  // allowWriteOutside: false`) would still get the loose behavior
291
293
  // because the legacy flag short-circuited the OR. Codex P1 #36.
292
- const granular = config?.workspace?.allowWriteOutside;
294
+ const granular = config.workspace?.allowWriteOutside;
293
295
  if (granular === true) return null;
294
296
  if (granular === false) return getWorkspaceRoots(config);
295
- if (config?.allowOutsideWorkspace === true) return null;
297
+ if (config.allowOutsideWorkspace === true) return null;
296
298
  return getWorkspaceRoots(config);
297
299
  }
298
300
 
@@ -302,14 +304,14 @@ export function getWriteRoots(
302
304
  * `allowOutsideWorkspace`) by returning `null`.
303
305
  */
304
306
  export function getReadRoots(
305
- config?: t.LocalExecutionConfig
307
+ config: t.LocalExecutionConfig = {}
306
308
  ): string[] | null {
307
309
  // Same precedence as getWriteRoots: granular flag is authoritative
308
310
  // when set, legacy flag is the fallback. Codex P1 #36.
309
- const granular = config?.workspace?.allowReadOutside;
311
+ const granular = config.workspace?.allowReadOutside;
310
312
  if (granular === true) return null;
311
313
  if (granular === false) return getWorkspaceRoots(config);
312
- if (config?.allowOutsideWorkspace === true) return null;
314
+ if (config.allowOutsideWorkspace === true) return null;
313
315
  return getWorkspaceRoots(config);
314
316
  }
315
317
 
@@ -323,10 +325,13 @@ const missingSandboxRuntimeMessage = [
323
325
  'Local sandbox is enabled, but @anthropic-ai/sandbox-runtime is not installed.',
324
326
  'Install it with `npm install @anthropic-ai/sandbox-runtime`, or disable local sandboxing with `local.sandbox.enabled: false`.',
325
327
  ].join(' ');
328
+ const sandboxRuntimePackage = '@anthropic-ai/sandbox-runtime';
326
329
 
327
330
  /** Lazy-loads the ESM-only sandbox runtime only when sandboxing is enabled. */
328
331
  function loadSandboxRuntime(): Promise<SandboxRuntimeModule> {
329
- sandboxRuntimePromise ??= import('@anthropic-ai/sandbox-runtime');
332
+ sandboxRuntimePromise ??= import(
333
+ sandboxRuntimePackage
334
+ ) as Promise<SandboxRuntimeModule>;
330
335
  return sandboxRuntimePromise;
331
336
  }
332
337
 
@@ -487,7 +492,9 @@ export async function validateBashCommand(
487
492
  }
488
493
 
489
494
  if (config.readOnly === true && mutatingCommandPattern.test(normalized)) {
490
- errors.push('Command appears to mutate files or repository state in read-only local mode.');
495
+ errors.push(
496
+ 'Command appears to mutate files or repository state in read-only local mode.'
497
+ );
491
498
  }
492
499
 
493
500
  // Use the same shell the actual execution path will use. Hard-coding
@@ -504,12 +511,14 @@ export async function validateBashCommand(
504
511
  sandbox: { enabled: false },
505
512
  },
506
513
  { internal: true }
507
- ).catch((error: Error): SpawnResult => ({
508
- stdout: '',
509
- stderr: error.message,
510
- exitCode: 1,
511
- timedOut: false,
512
- }));
514
+ ).catch(
515
+ (error: Error): SpawnResult => ({
516
+ stdout: '',
517
+ stderr: error.message,
518
+ exitCode: 1,
519
+ timedOut: false,
520
+ })
521
+ );
513
522
 
514
523
  if (syntax.exitCode !== 0) {
515
524
  errors.push(
@@ -567,14 +576,7 @@ async function ensureSandbox(
567
576
  await runtime.SandboxManager.reset();
568
577
  }
569
578
 
570
- // Cast at the runtime boundary — our public `BuiltSandboxRuntimeConfig`
571
- // is intentionally structural to keep the optional peer dep out of
572
- // generated `.d.ts` (Codex P1 #22). It's a structural subset of the
573
- // peer's `SandboxRuntimeConfig`, so the assignment is sound at the
574
- // one site where the peer is actually loaded.
575
- await runtime.SandboxManager.initialize(
576
- runtimeConfig as unknown as SandboxRuntimeConfig
577
- );
579
+ await runtime.SandboxManager.initialize(runtimeConfig);
578
580
  sandboxInitialized = true;
579
581
  sandboxConfigKey = nextKey;
580
582
  return runtime.SandboxManager;
@@ -715,8 +717,7 @@ export async function spawnLocalProcess(
715
717
  // so a process producing unbounded output gets stopped instead of
716
718
  // letting the host OOM.
717
719
  const inMemoryCapBytes = maxOutputChars * 2;
718
- const hardKillBytes =
719
- config.maxSpawnedBytes ?? DEFAULT_MAX_SPAWNED_BYTES;
720
+ const hardKillBytes = config.maxSpawnedBytes ?? DEFAULT_MAX_SPAWNED_BYTES;
720
721
  const sandboxManager = await ensureSandbox(config, cwd);
721
722
  // Internal probes (validateBashCommand syntax preflight,
722
723
  // isRipgrepAvailable, syntax-check probe cache priming) pass
@@ -775,10 +776,7 @@ export async function spawnLocalProcess(
775
776
  spillStream.write('\n===== overflow stream begins here =====\n');
776
777
  };
777
778
 
778
- const handleChunk = (
779
- buf: Buffer,
780
- kind: 'stdout' | 'stderr'
781
- ): void => {
779
+ const handleChunk = (buf: Buffer, kind: 'stdout' | 'stderr'): void => {
782
780
  totalSpawnedBytes += buf.length;
783
781
  // hardKillBytes <= 0 means "no cap" per the public config contract
784
782
  // (see LocalExecutionConfig.maxSpawnedBytes). Skip the kill check
@@ -857,11 +855,11 @@ export async function spawnLocalProcess(
857
855
  }, timeoutMs);
858
856
  }
859
857
 
860
- child.stdout?.on('data', (chunk: Buffer) => {
858
+ child.stdout.on('data', (chunk: Buffer) => {
861
859
  handleChunk(chunk, 'stdout');
862
860
  });
863
861
 
864
- child.stderr?.on('data', (chunk: Buffer) => {
862
+ child.stderr.on('data', (chunk: Buffer) => {
865
863
  handleChunk(chunk, 'stderr');
866
864
  });
867
865
 
@@ -928,8 +926,7 @@ export async function executeLocalBash(
928
926
  * Codex P1 [45], extended for dot-glob in Codex P1 [47] (mirrors the
929
927
  * `DESTRUCTIVE_TARGET` suffix matrix exactly).
930
928
  */
931
- const PROTECTED_TARGET_ARG_RE =
932
- /^(?:\/|~|\$\{?HOME\}?|\.)(?:\/?\.?\*|\/)?$/;
929
+ const PROTECTED_TARGET_ARG_RE = /^(?:\/|~|\$\{?HOME\}?|\.)(?:\/?\.?\*|\/)?$/;
933
930
 
934
931
  /**
935
932
  * Mutating-op recognizer for the args check. Conservative: only the
@@ -971,11 +968,7 @@ export async function executeLocalBashWithArgs(
971
968
  }
972
969
  }
973
970
  const shell = config.shell ?? DEFAULT_SHELL;
974
- return spawnLocalProcess(
975
- shell,
976
- ['-lc', command, '--', ...args],
977
- config
978
- );
971
+ return spawnLocalProcess(shell, ['-lc', command, '--', ...args], config);
979
972
  }
980
973
 
981
974
  export async function executeLocalCode(
@@ -1009,7 +1002,11 @@ export async function executeLocalCode(
1009
1002
  config.shell
1010
1003
  );
1011
1004
  if (runtime.source != null) {
1012
- await writeFile(resolve(tempDir, runtime.fileName), runtime.source, 'utf8');
1005
+ await writeFile(
1006
+ resolve(tempDir, runtime.fileName),
1007
+ runtime.source,
1008
+ 'utf8'
1009
+ );
1013
1010
  }
1014
1011
  return await spawnLocalProcess(runtime.command, runtime.args, config);
1015
1012
  } finally {
@@ -1205,7 +1202,7 @@ function killProcessTree(child: ChildProcess): void {
1205
1202
  // window. Use unref() so the timer doesn't keep the Node process
1206
1203
  // alive past the parent's natural exit.
1207
1204
  const escalation = setTimeout(() => sigkill(child), SIGKILL_ESCALATION_MS);
1208
- escalation.unref?.();
1205
+ escalation.unref();
1209
1206
  child.once('close', () => clearTimeout(escalation));
1210
1207
  }
1211
1208
 
@@ -1225,9 +1222,12 @@ export function resolveWorkspacePath(
1225
1222
  intent: 'read' | 'write' = 'write'
1226
1223
  ): string {
1227
1224
  const cwd = getLocalCwd(config);
1228
- const absolutePath = isAbsolute(filePath) ? resolve(filePath) : resolve(cwd, filePath);
1225
+ const absolutePath = isAbsolute(filePath)
1226
+ ? resolve(filePath)
1227
+ : resolve(cwd, filePath);
1229
1228
 
1230
- const roots = intent === 'write' ? getWriteRoots(config) : getReadRoots(config);
1229
+ const roots =
1230
+ intent === 'write' ? getWriteRoots(config) : getReadRoots(config);
1231
1231
  if (roots == null) return absolutePath; // explicit allow-outside
1232
1232
 
1233
1233
  if (absolutePath === cwd || isInsideAnyRoot(absolutePath, roots)) {
@@ -1306,7 +1306,8 @@ export async function resolveWorkspacePathSafe(
1306
1306
  intent: 'read' | 'write' = 'write'
1307
1307
  ): Promise<string> {
1308
1308
  const lexical = resolveWorkspacePath(filePath, config, intent);
1309
- const roots = intent === 'write' ? getWriteRoots(config) : getReadRoots(config);
1309
+ const roots =
1310
+ intent === 'write' ? getWriteRoots(config) : getReadRoots(config);
1310
1311
  if (roots == null) {
1311
1312
  return lexical;
1312
1313
  }
@@ -115,7 +115,7 @@ export function createLocalCodeExecutionTool(
115
115
  {
116
116
  session_id: getLocalSessionId(config),
117
117
  files: [],
118
- },
118
+ } satisfies t.CodeExecutionArtifact,
119
119
  ];
120
120
  },
121
121
  {
@@ -151,7 +151,7 @@ export function createLocalBashExecutionTool(options?: {
151
151
  {
152
152
  session_id: getLocalSessionId(config),
153
153
  files: [],
154
- },
154
+ } satisfies t.CodeExecutionArtifact,
155
155
  ];
156
156
  },
157
157
  {
@@ -79,7 +79,9 @@ type LocalProgrammaticParams = {
79
79
 
80
80
  type ToolFilter = (toolDefs: t.LCTool[], code: string) => t.LCTool[];
81
81
 
82
- function resolveRuntime(params: LocalProgrammaticParams): LocalProgrammaticRuntime {
82
+ function resolveRuntime(
83
+ params: LocalProgrammaticParams
84
+ ): LocalProgrammaticRuntime {
83
85
  const rawRuntime = params.lang ?? params.runtime ?? params.language ?? 'bash';
84
86
  return rawRuntime === 'py' || rawRuntime === 'python' ? 'python' : 'bash';
85
87
  }
@@ -494,13 +496,17 @@ async function runLocalProgrammaticTool(args: {
494
496
  localConfig: t.LocalExecutionConfig;
495
497
  runtime: LocalProgrammaticRuntime;
496
498
  }): Promise<[string, t.ProgrammaticExecutionArtifact]> {
497
- const { toolMap, toolDefs, hookContext } = getProgrammaticContext(args.config);
499
+ const { toolMap, toolDefs, hookContext } = getProgrammaticContext(
500
+ args.config
501
+ );
498
502
 
499
503
  if (toolMap == null || toolMap.size === 0) {
500
504
  throw new Error('No toolMap provided for local programmatic execution.');
501
505
  }
502
506
  if (toolDefs == null || toolDefs.length === 0) {
503
- throw new Error('No tool definitions provided for local programmatic execution.');
507
+ throw new Error(
508
+ 'No tool definitions provided for local programmatic execution.'
509
+ );
504
510
  }
505
511
 
506
512
  const { effectiveTools, effectiveMap } = createEffectiveToolMap(
@@ -512,17 +518,28 @@ async function runLocalProgrammaticTool(args: {
512
518
  const bridge = await createToolBridge(effectiveMap, hookContext);
513
519
 
514
520
  try {
515
- const timeoutMs = args.params.timeout ?? args.localConfig.timeoutMs ?? DEFAULT_TIMEOUT;
521
+ const timeoutMs =
522
+ args.params.timeout ?? args.localConfig.timeoutMs ?? DEFAULT_TIMEOUT;
516
523
  const result =
517
524
  args.runtime === 'bash'
518
525
  ? await executeLocalBash(
519
- createBashProgram(args.params.code, effectiveTools, bridge.url, bridge.token),
526
+ createBashProgram(
527
+ args.params.code,
528
+ effectiveTools,
529
+ bridge.url,
530
+ bridge.token
531
+ ),
520
532
  { ...args.localConfig, timeoutMs }
521
533
  )
522
534
  : await executeLocalCode(
523
535
  {
524
536
  lang: 'py',
525
- code: createPythonProgram(args.params.code, effectiveTools, bridge.url, bridge.token),
537
+ code: createPythonProgram(
538
+ args.params.code,
539
+ effectiveTools,
540
+ bridge.url,
541
+ bridge.token
542
+ ),
526
543
  },
527
544
  { ...args.localConfig, timeoutMs }
528
545
  );
@@ -0,0 +1,15 @@
1
+ declare module 'diff' {
2
+ export type PatchOptions = {
3
+ context?: number;
4
+ };
5
+
6
+ export function createTwoFilesPatch(
7
+ oldFileName: string,
8
+ newFileName: string,
9
+ oldStr: string,
10
+ newStr: string,
11
+ oldHeader?: string,
12
+ newHeader?: string,
13
+ options?: PatchOptions
14
+ ): string;
15
+ }
@@ -138,42 +138,90 @@ export type ToolEndEvent = {
138
138
  type?: 'tool_call';
139
139
  };
140
140
 
141
- export type CodeEnvFile = {
141
+ /**
142
+ * Closed set of resource kinds for sandbox file caching. Defined as a
143
+ * `as const` tuple so the runtime list and the TypeScript union can't
144
+ * drift on future additions — adding a new kind to the tuple updates
145
+ * both at once.
146
+ *
147
+ * - `skill`: shared per skill identity. Cross-user-within-tenant
148
+ * sharing. Codeapi sessionKey omits the user dimension. `version`
149
+ * is required (skill's monotonic counter scopes cache per revision).
150
+ * - `agent`: shared per agent identity. Same sharing semantic as
151
+ * skills.
152
+ * - `user`: user-private. Codeapi sessionKey is keyed by the
153
+ * requesting user from auth context. Used for chat attachments
154
+ * and code-output files.
155
+ */
156
+ export const CODE_ENV_KINDS = ['skill', 'agent', 'user'] as const;
157
+ export type CodeEnvKind = (typeof CODE_ENV_KINDS)[number];
158
+
159
+ type CodeEnvFileBase = {
160
+ /**
161
+ * Resource identity. Semantics depend on `kind`:
162
+ * - `skill`: skill `_id` (sessionKey-meaningful, cross-user shared).
163
+ * - `agent`: agent id (sessionKey-meaningful, cross-user shared).
164
+ * - `user`: informational only — codeapi derives sessionKey from
165
+ * the auth-context user. Kept on the type for shape uniformity;
166
+ * do not rely on it for routing.
167
+ */
142
168
  id: string;
143
169
  name: string;
144
- session_id: string;
145
170
  /**
146
- * Identifier of the entity that owns this file's session (skill id,
147
- * agent id, etc). Forwarded to codeapi so it can resolve the
148
- * per-file sessionKey instead of falling back to a single
149
- * request-level entity. Required when a single execute request
150
- * references files uploaded under different entities (e.g. a skill
151
- * file plus a user attachment in the same call).
171
+ * Storage session the long-lived bucket where this file's bytes
172
+ * live in object storage. Distinct from the (transient) execution
173
+ * session id that appears at the top level of an execute response;
174
+ * the two used to share the field name `session_id` and the
175
+ * conflation caused real bugs. See codeapi #1455 / agents #148.
152
176
  */
153
- entity_id?: string;
177
+ storage_session_id: string;
154
178
  };
155
179
 
180
+ /**
181
+ * `CodeEnvFile` is a discriminated union on `kind`. `version` is
182
+ * statically required for `kind: 'skill'` and statically forbidden
183
+ * for `agent` / `user` — the constraint holds at compile time on
184
+ * every consumer, not just on codeapi's runtime validator.
185
+ *
186
+ * Codeapi switches on `kind` to derive the sessionKey for cache
187
+ * scoping (`<tenant>:<kind>:<id>[:v:<version>]`). Cross-user sharing
188
+ * for `kind: 'skill'` / `'agent'` is a designed property of the
189
+ * kind switch.
190
+ */
191
+ export type CodeEnvFile =
192
+ | (CodeEnvFileBase & { kind: 'skill'; version: number })
193
+ | (CodeEnvFileBase & { kind: 'agent' })
194
+ | (CodeEnvFileBase & { kind: 'user' });
195
+
156
196
  export type CodeExecutionToolParams =
157
197
  | undefined
158
198
  | {
199
+ /** Execution session — see `CodeSessionContext.session_id`. */
159
200
  session_id?: string;
160
201
  user_id?: string;
161
202
  files?: CodeEnvFile[];
162
203
  };
163
204
 
164
205
  export type FileRef = {
206
+ /**
207
+ * Resource identity. Semantics depend on `kind` (when present):
208
+ * - `skill` / `agent`: shared resource id (sessionKey-meaningful).
209
+ * - `user`: informational only — codeapi derives sessionKey from
210
+ * the auth-context user. Do not rely on it for routing.
211
+ */
165
212
  id: string;
166
213
  name: string;
167
214
  path?: string;
168
- /** Session ID this file belongs to (for multi-session file tracking) */
169
- session_id?: string;
170
215
  /**
171
- * Entity that owns this file's session (skill id, agent id, etc).
172
- * Carried on tracked session files so it can flow through to
173
- * `_injected_files` when a subsequent execute references a mix of
174
- * files uploaded under different entities.
216
+ * Storage session this file lives in. See `CodeEnvFile.storage_session_id`
217
+ * for the full motivation.
175
218
  */
176
- entity_id?: string;
219
+ storage_session_id?: string;
220
+ /** Resource kind — see `CodeEnvFile.kind`. */
221
+ kind?: CodeEnvKind;
222
+ /** Resource version — see `CodeEnvFile.version`. Only meaningful when
223
+ * `kind === 'skill'`. */
224
+ version?: number;
177
225
  /**
178
226
  * `true` when the codeapi sandbox echoed this entry as an unchanged
179
227
  * passthrough of an input the caller already owns (skill files,
@@ -188,6 +236,11 @@ export type FileRef = {
188
236
  export type FileRefs = FileRef[];
189
237
 
190
238
  export type ExecuteResult = {
239
+ /**
240
+ * Execution session id — the (transient) sandbox run that produced
241
+ * this output. Distinct from per-file `storage_session_id` on the
242
+ * files array.
243
+ */
191
244
  session_id: string;
192
245
  stdout: string;
193
246
  stderr: string;
@@ -254,6 +307,7 @@ export type ToolCallRequest = {
254
307
  turn?: number;
255
308
  /** Code execution session context for session continuity in event-driven mode */
256
309
  codeSessionContext?: {
310
+ /** Execution session — see `CodeSessionContext.session_id`. */
257
311
  session_id: string;
258
312
  files?: CodeEnvFile[];
259
313
  };
@@ -775,6 +829,7 @@ export type PTCToolResult = {
775
829
  */
776
830
  export type ProgrammaticExecutionResponse = {
777
831
  status: 'tool_call_required' | 'completed' | 'error' | unknown;
832
+ /** Execution session — see `CodeSessionContext.session_id`. */
778
833
  session_id?: string;
779
834
 
780
835
  /** Present when status='tool_call_required' */
@@ -794,6 +849,7 @@ export type ProgrammaticExecutionResponse = {
794
849
  * Artifact returned by the PTC tool
795
850
  */
796
851
  export type ProgrammaticExecutionArtifact = {
852
+ /** Execution session — see `CodeSessionContext.session_id`. */
797
853
  session_id?: string;
798
854
  files?: FileRefs;
799
855
  };
@@ -827,7 +883,12 @@ export type ProgrammaticToolCallingParams = {
827
883
  * Stored in Graph.sessions and injected into subsequent tool invocations.
828
884
  */
829
885
  export type CodeSessionContext = {
830
- /** Session ID from the code execution environment */
886
+ /**
887
+ * Execution session id — the (transient) sandbox run id. Used by
888
+ * ToolNode to thread session continuity into the next code-execution
889
+ * tool call. Distinct from per-file `storage_session_id` carried on
890
+ * `files`.
891
+ */
831
892
  session_id: string;
832
893
  /** Files generated in this session (for context/tracking) */
833
894
  files?: FileRefs;
@@ -840,6 +901,7 @@ export type CodeSessionContext = {
840
901
  * Used to extract session context after tool completion.
841
902
  */
842
903
  export type CodeExecutionArtifact = {
904
+ /** Execution session — see `CodeSessionContext.session_id`. */
843
905
  session_id?: string;
844
906
  files?: FileRefs;
845
907
  };