@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.
- package/CHANGELOG.md +21 -0
- package/dist/bridge/index.mjs +160 -22
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +23 -32
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/bridge/index.ts +49 -28
- package/src/bridge/persistent-memory-saver.ts +137 -0
- package/src/deepagents-bootstrap.ts +19 -15
- package/src/deepagents-harness.ts +9 -21
|
@@ -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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
?
|
|
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
|
|
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',
|