@ai-sdk/harness-deepagents 1.0.93 → 1.0.95

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.
@@ -0,0 +1,137 @@
1
+ import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
2
+ import { dirname } from 'node:path';
3
+ import type { MemorySaver } from '@langchain/langgraph';
4
+
5
+ const SNAPSHOT_HEADER = 'deepagents-memory-saver-v1';
6
+
7
+ function encodeString(value: string): string {
8
+ return Buffer.from(value, 'utf8').toString('base64');
9
+ }
10
+
11
+ function decodeString(value: string): string {
12
+ return Buffer.from(value, 'base64').toString('utf8');
13
+ }
14
+
15
+ function encodeBytes(value: Uint8Array): string {
16
+ return Buffer.from(value).toString('base64');
17
+ }
18
+
19
+ function decodeBytes(value: string): Uint8Array {
20
+ return Buffer.from(value, 'base64');
21
+ }
22
+
23
+ export async function loadMemorySaver({
24
+ path,
25
+ saver,
26
+ }: {
27
+ path: string;
28
+ saver: MemorySaver;
29
+ }): Promise<void> {
30
+ let snapshot: string;
31
+ try {
32
+ snapshot = await readFile(path, 'utf8');
33
+ } catch (error) {
34
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return;
35
+ throw error;
36
+ }
37
+
38
+ const [header, ...lines] = snapshot.split('\n');
39
+ if (header !== SNAPSHOT_HEADER) {
40
+ throw new Error('Unsupported Deep Agents conversation checkpoint format');
41
+ }
42
+
43
+ const storage: MemorySaver['storage'] = Object.create(null);
44
+ const writes: MemorySaver['writes'] = Object.create(null);
45
+
46
+ for (const line of lines) {
47
+ if (line === '') continue;
48
+ const fields = line.split('\t');
49
+ if (fields[0] === 'S' && fields.length === 7) {
50
+ const [, threadIdValue, namespaceValue, checkpointIdValue] = fields;
51
+ const threadId = decodeString(threadIdValue);
52
+ const namespace = decodeString(namespaceValue);
53
+ const checkpointId = decodeString(checkpointIdValue);
54
+ storage[threadId] ??= Object.create(null);
55
+ storage[threadId][namespace] ??= Object.create(null);
56
+ storage[threadId][namespace][checkpointId] = [
57
+ decodeBytes(fields[4]),
58
+ decodeBytes(fields[5]),
59
+ fields[6] === '' ? undefined : decodeString(fields[6]),
60
+ ];
61
+ continue;
62
+ }
63
+ if (fields[0] === 'W' && fields.length === 6) {
64
+ const [, keyValue, indexValue, taskIdValue, channelValue, value] = fields;
65
+ const key = decodeString(keyValue);
66
+ const index = decodeString(indexValue);
67
+ writes[key] ??= Object.create(null);
68
+ writes[key][index] = [
69
+ decodeString(taskIdValue),
70
+ decodeString(channelValue),
71
+ decodeBytes(value),
72
+ ];
73
+ continue;
74
+ }
75
+ throw new Error('Invalid Deep Agents conversation checkpoint');
76
+ }
77
+
78
+ saver.storage = storage;
79
+ saver.writes = writes;
80
+ }
81
+
82
+ export async function saveMemorySaver({
83
+ path,
84
+ saver,
85
+ }: {
86
+ path: string;
87
+ saver: MemorySaver;
88
+ }): Promise<void> {
89
+ const lines = [SNAPSHOT_HEADER];
90
+
91
+ for (const [threadId, namespaces] of Object.entries(saver.storage)) {
92
+ for (const [namespace, checkpoints] of Object.entries(namespaces)) {
93
+ for (const [
94
+ checkpointId,
95
+ [checkpoint, metadata, parentCheckpointId],
96
+ ] of Object.entries(checkpoints)) {
97
+ lines.push(
98
+ [
99
+ 'S',
100
+ encodeString(threadId),
101
+ encodeString(namespace),
102
+ encodeString(checkpointId),
103
+ encodeBytes(checkpoint),
104
+ encodeBytes(metadata),
105
+ parentCheckpointId == null ? '' : encodeString(parentCheckpointId),
106
+ ].join('\t'),
107
+ );
108
+ }
109
+ }
110
+ }
111
+
112
+ for (const [key, indexedWrites] of Object.entries(saver.writes)) {
113
+ for (const [index, [taskId, channel, value]] of Object.entries(
114
+ indexedWrites,
115
+ )) {
116
+ lines.push(
117
+ [
118
+ 'W',
119
+ encodeString(key),
120
+ encodeString(index),
121
+ encodeString(taskId),
122
+ encodeString(channel),
123
+ encodeBytes(value),
124
+ ].join('\t'),
125
+ );
126
+ }
127
+ }
128
+
129
+ await mkdir(dirname(path), { recursive: true });
130
+ const temporaryPath = `${path}.${process.pid}.tmp`;
131
+ await writeFile(temporaryPath, `${lines.join('\n')}\n`, 'utf8');
132
+ await rename(temporaryPath, path);
133
+ }
134
+
135
+ export async function removeMemorySaverSnapshot(path: string): Promise<void> {
136
+ await rm(path, { force: true });
137
+ }
@@ -69,19 +69,23 @@ function installRipgrepCommand(): string {
69
69
  }
70
70
 
71
71
  async function readBridgeAsset(name: string): Promise<string> {
72
- const candidates = [
73
- new URL(`./bridge/${name}`, import.meta.url),
74
- new URL(`../bridge/${name}`, import.meta.url),
75
- ];
76
- let lastErr: unknown;
77
- for (const url of candidates) {
78
- try {
79
- return await readFile(fileURLToPath(url), 'utf8');
80
- } catch (err) {
81
- const code = (err as NodeJS.ErrnoException).code;
82
- if (code !== 'ENOENT') throw err;
83
- lastErr = err;
84
- }
85
- }
86
- throw lastErr ?? new Error(`bridge asset not found: ${name}`);
72
+ return readFile(
73
+ fileURLToPath(
74
+ resolveBridgeAssetUrl({
75
+ name,
76
+ moduleUrl: import.meta.url,
77
+ }),
78
+ ),
79
+ 'utf8',
80
+ );
81
+ }
82
+
83
+ export function resolveBridgeAssetUrl({
84
+ name,
85
+ moduleUrl,
86
+ }: {
87
+ name: string;
88
+ moduleUrl: string | URL;
89
+ }): URL {
90
+ return new URL(`./bridge/${name}`, moduleUrl);
87
91
  }
@@ -1,4 +1,3 @@
1
- import { randomBytes } from 'node:crypto';
2
1
  import { posix } from 'node:path';
3
2
  import {
4
3
  commonTool,
@@ -21,6 +20,7 @@ import {
21
20
  } from '@ai-sdk/harness';
22
21
  import {
23
22
  applyCredentialForwarding,
23
+ createBridgeToken,
24
24
  createSandboxCredentialEnvironment,
25
25
  markBridgeStarting,
26
26
  createBridgeErrorHandler,
@@ -34,6 +34,7 @@ import {
34
34
  shellQuote,
35
35
  warnCredentialBrokeringUnavailable,
36
36
  waitForBridgeReady,
37
+ withBridgeToken,
37
38
  writeSkills as writeHarnessSkills,
38
39
  type WriteSkillsResult,
39
40
  } from '@ai-sdk/harness/utils';
@@ -222,7 +223,6 @@ export function createDeepAgents(
222
223
  lifecycleStateSchema: deepAgentsResumeStateSchema,
223
224
  getBootstrap: getDeepAgentsBootstrap,
224
225
  doStart: async startOpts => {
225
- const model = startOpts.model ?? settings.model;
226
226
  const permissionMode = startOpts.permissionMode;
227
227
  const sandboxSession = startOpts.sandboxSession;
228
228
  const toolSafeSandboxSession =
@@ -355,7 +355,7 @@ export function createDeepAgents(
355
355
  sessionId: startOpts.sessionId,
356
356
  channel: attachChannel,
357
357
  proc: undefined,
358
- model,
358
+ model: settings.model,
359
359
  thinking: settings.thinking,
360
360
  effort: settings.effort,
361
361
  bridgePort: coords.port,
@@ -382,7 +382,7 @@ export function createDeepAgents(
382
382
  });
383
383
  const token =
384
384
  settings.mintBridgeToken == null
385
- ? randomBytes(32).toString('hex')
385
+ ? createBridgeToken()
386
386
  : settings.mintBridgeToken(sandboxId!);
387
387
 
388
388
  const forwardedAuthEnvironment = credentialsBrokered
@@ -413,7 +413,7 @@ export function createDeepAgents(
413
413
  });
414
414
 
415
415
  const proc = await toolSafeSandboxSession.spawn({
416
- command: `node ${shellQuote(`${bootstrapDir}/bridge.mjs`)} --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)} --bootstrap-dir ${shellQuote(bootstrapDir)}`,
416
+ command: `node ${shellQuote(`${bootstrapDir}/bridge.mjs`)} --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)} --bootstrap-dir ${shellQuote(bootstrapDir)}${isResume ? ' --resume true' : ''}`,
417
417
  env,
418
418
  abortSignal: startOpts.abortSignal,
419
419
  });
@@ -470,7 +470,7 @@ export function createDeepAgents(
470
470
  sessionId: startOpts.sessionId,
471
471
  channel,
472
472
  proc,
473
- model,
473
+ model: settings.model,
474
474
  thinking: settings.thinking,
475
475
  effort: settings.effort,
476
476
  bridgePort: boundPort,
@@ -606,18 +606,6 @@ function openWebSocket({
606
606
  });
607
607
  }
608
608
 
609
- function withBridgeToken({
610
- endpoint,
611
- token,
612
- }: {
613
- endpoint: HarnessV1PortEndpoint;
614
- token: string;
615
- }): HarnessV1PortEndpoint {
616
- const bridgeUrl = new URL(endpoint.url);
617
- bridgeUrl.searchParams.set('agent_bridge_token', token);
618
- return { ...endpoint, url: bridgeUrl.toString() };
619
- }
620
-
621
609
  function createSession({
622
610
  sessionId,
623
611
  channel,
@@ -785,7 +773,6 @@ function createSession({
785
773
  return {
786
774
  sessionId,
787
775
  isResume,
788
- modelId: model,
789
776
  doPromptTurn: async promptOpts => {
790
777
  if (
791
778
  promptOpts.responseFormat?.type === 'json' &&
@@ -822,7 +809,9 @@ function createSession({
822
809
  ...(promptOpts.responseFormat == null
823
810
  ? {}
824
811
  : { responseFormat: promptOpts.responseFormat }),
825
- ...(model ? { model } : {}),
812
+ ...((promptOpts.model ?? model)
813
+ ? { model: promptOpts.model ?? model }
814
+ : {}),
826
815
  ...(thinking ? { thinking } : {}),
827
816
  ...(effort ? { effort } : {}),
828
817
  ...(skillsPaths?.length ? { skillsPaths } : {}),
@@ -915,7 +904,6 @@ function createSession({
915
904
  }
916
905
  stopped = true;
917
906
  await teardown({ channel, proc, operation: 'stop' });
918
- // In-memory conversation is lost on teardown; the sandbox snapshot preserves the workspace files, not the conversation.
919
907
  const payload: HarnessV1ResumeSessionState = {
920
908
  type: 'resume-session',
921
909
  harnessId: 'deepagents',