@ai-sdk/harness-deepagents 0.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,741 @@
1
+ // src/deepagents-harness.ts
2
+ import { randomBytes } from "crypto";
3
+ import { readFile } from "fs/promises";
4
+ import { fileURLToPath } from "url";
5
+ import {
6
+ commonTool,
7
+ HarnessCapabilityUnsupportedError,
8
+ harnessV1DiagnosticFromBridgeFrame
9
+ } from "@ai-sdk/harness";
10
+ import {
11
+ markBridgeStarting,
12
+ SandboxChannel,
13
+ waitForBridgeReady
14
+ } from "@ai-sdk/harness/utils";
15
+ import { tool } from "@ai-sdk/provider-utils";
16
+ import { WebSocket } from "ws";
17
+ import { z as z2 } from "zod";
18
+
19
+ // src/deepagents-auth.ts
20
+ import { getAiGatewayAuthFromEnv } from "@ai-sdk/harness/utils";
21
+ function resolveDeepAgentsEnv({
22
+ auth,
23
+ processEnv = process.env
24
+ }) {
25
+ if (auth?.anthropic) {
26
+ return pickAnthropic({ explicit: auth.anthropic, processEnv });
27
+ }
28
+ const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({ env: processEnv });
29
+ if (auth?.gateway) {
30
+ return pickGateway({ explicit: auth.gateway, gatewayAuthFromEnv });
31
+ }
32
+ if (gatewayAuthFromEnv.apiKey) {
33
+ return pickGateway({ explicit: {}, gatewayAuthFromEnv });
34
+ }
35
+ return pickAnthropic({ processEnv });
36
+ }
37
+ function pickAnthropic({
38
+ explicit,
39
+ processEnv
40
+ }) {
41
+ const env = {};
42
+ const apiKey = explicit?.apiKey ?? processEnv.ANTHROPIC_API_KEY;
43
+ if (apiKey) env.ANTHROPIC_API_KEY = apiKey;
44
+ const authToken = explicit?.authToken ?? processEnv.ANTHROPIC_AUTH_TOKEN;
45
+ if (authToken) env.ANTHROPIC_AUTH_TOKEN = authToken;
46
+ const baseUrl = explicit?.baseUrl ?? processEnv.ANTHROPIC_BASE_URL;
47
+ if (baseUrl) env.ANTHROPIC_BASE_URL = baseUrl;
48
+ return env;
49
+ }
50
+ function pickGateway({
51
+ explicit,
52
+ gatewayAuthFromEnv
53
+ }) {
54
+ const apiKey = explicit.apiKey ?? gatewayAuthFromEnv.apiKey;
55
+ const baseUrl = (explicit.baseUrl ?? gatewayAuthFromEnv.baseUrl).replace(
56
+ /\/+$/,
57
+ ""
58
+ );
59
+ const env = {};
60
+ if (apiKey) {
61
+ env.AI_GATEWAY_API_KEY = apiKey;
62
+ env.ANTHROPIC_API_KEY = apiKey;
63
+ }
64
+ env.ANTHROPIC_BASE_URL = baseUrl;
65
+ return env;
66
+ }
67
+
68
+ // src/deepagents-bridge-protocol.ts
69
+ import {
70
+ harnessV1BridgeInboundCommandSchemas,
71
+ harnessV1BridgeOutboundMessageSchema,
72
+ harnessV1BridgeReadySchema,
73
+ harnessV1BridgeStartBaseSchema
74
+ } from "@ai-sdk/harness";
75
+ import { z } from "zod/v4";
76
+ var outboundMessageSchema = harnessV1BridgeOutboundMessageSchema;
77
+ var startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
78
+ // Prepended to the first user message (createDeepAgent takes no instructions param).
79
+ instructions: z.string().optional(),
80
+ // In-backend skills source dirs ($HOME and <workDir>), passed to createDeepAgent({ skills }).
81
+ skillsPaths: z.array(z.string()).optional(),
82
+ // Max LangGraph super-steps per turn (streamEvents recursionLimit).
83
+ recursionLimit: z.number().optional()
84
+ });
85
+ var inboundMessageSchema = z.discriminatedUnion("type", [
86
+ startMessageSchema,
87
+ ...harnessV1BridgeInboundCommandSchemas
88
+ ]);
89
+
90
+ // src/deepagents-harness.ts
91
+ var BOOTSTRAP_DIR = "/tmp/harness/deepagents";
92
+ var RIPGREP_VERSION = "14.1.1";
93
+ var RIPGREP_SHA256_X64 = "4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e";
94
+ var RIPGREP_SHA256_ARM = "c827481c4ff4ea10c9dc7a4022c8de5db34a5737cb74484d62eb94a95841ab2f";
95
+ function installRipgrepCommand() {
96
+ const v = RIPGREP_VERSION;
97
+ return [
98
+ "command -v rg >/dev/null 2>&1 || {",
99
+ 'case "$(uname -m)" in',
100
+ `aarch64) a=aarch64-unknown-linux-gnu; sha=${RIPGREP_SHA256_ARM} ;;`,
101
+ `*) a=x86_64-unknown-linux-musl; sha=${RIPGREP_SHA256_X64} ;;`,
102
+ "esac;",
103
+ `f=/tmp/ripgrep-${v}.tar.gz;`,
104
+ `curl -fsSL "https://github.com/BurntSushi/ripgrep/releases/download/${v}/ripgrep-${v}-$a.tar.gz" -o "$f"`,
105
+ '&& echo "$sha $f" | sha256sum -c -',
106
+ '&& tar xzf "$f" -C /tmp',
107
+ `&& mv "/tmp/ripgrep-${v}-$a/rg" /usr/local/bin/rg && chmod +x /usr/local/bin/rg;`,
108
+ "}"
109
+ ].join(" ");
110
+ }
111
+ var SKILLS_SOURCE_PATH = "/.agents/skills";
112
+ var deepAgentsBridgeCoordsSchema = z2.object({
113
+ port: z2.number(),
114
+ token: z2.string(),
115
+ lastSeenEventId: z2.number(),
116
+ sandboxId: z2.string().optional()
117
+ });
118
+ var deepAgentsResumeStateSchema = z2.object({
119
+ bridge: deepAgentsBridgeCoordsSchema.optional()
120
+ });
121
+ var DEEPAGENTS_BUILTIN_TOOLS = {
122
+ read: commonTool("read", {
123
+ nativeName: "read_file",
124
+ toolUseKind: "readonly",
125
+ description: "Read file contents",
126
+ inputSchema: z2.object({ file_path: z2.string() })
127
+ }),
128
+ write: commonTool("write", {
129
+ nativeName: "write_file",
130
+ toolUseKind: "edit",
131
+ description: "Create a file",
132
+ inputSchema: z2.object({ file_path: z2.string(), content: z2.string() })
133
+ }),
134
+ edit: commonTool("edit", {
135
+ nativeName: "edit_file",
136
+ toolUseKind: "edit",
137
+ description: "Perform exact string replacements in a file",
138
+ inputSchema: z2.object({
139
+ file_path: z2.string(),
140
+ old_string: z2.string(),
141
+ new_string: z2.string()
142
+ })
143
+ }),
144
+ bash: commonTool("bash", {
145
+ nativeName: "execute",
146
+ toolUseKind: "bash",
147
+ description: "Run a shell command",
148
+ inputSchema: z2.object({ command: z2.string() })
149
+ }),
150
+ grep: commonTool("grep", {
151
+ nativeName: "grep",
152
+ toolUseKind: "readonly",
153
+ description: "Search file contents",
154
+ inputSchema: z2.object({ pattern: z2.string() })
155
+ }),
156
+ glob: commonTool("glob", {
157
+ nativeName: "glob",
158
+ toolUseKind: "readonly",
159
+ description: "Find files matching a glob pattern",
160
+ inputSchema: z2.object({ pattern: z2.string() })
161
+ }),
162
+ // No common-name equivalent — keyed by native name.
163
+ ls: tool({
164
+ description: "List files in a directory",
165
+ inputSchema: z2.object({ path: z2.string().optional() })
166
+ }),
167
+ task: tool({
168
+ description: "Spawn a subagent to handle a delegated task",
169
+ inputSchema: z2.object({
170
+ description: z2.string().optional(),
171
+ subagent_type: z2.string().optional()
172
+ })
173
+ }),
174
+ write_todos: tool({
175
+ description: "Manage a structured todo list",
176
+ inputSchema: z2.object({ todos: z2.array(z2.unknown()).optional() })
177
+ })
178
+ };
179
+ function createDeepAgents(settings = {}) {
180
+ let cachedBootstrap;
181
+ return {
182
+ specificationVersion: "harness-v1",
183
+ harnessId: "deepagents",
184
+ builtinTools: DEEPAGENTS_BUILTIN_TOOLS,
185
+ // Built-in tool approvals are gated in-bridge via DeepAgents' interruptOn (HITL) middleware.
186
+ supportsBuiltinToolApprovals: true,
187
+ lifecycleStateSchema: deepAgentsResumeStateSchema,
188
+ getBootstrap: async () => {
189
+ if (cachedBootstrap != null) return cachedBootstrap;
190
+ const [bridge, pkg, lock] = await Promise.all([
191
+ readBridgeAsset("index.mjs"),
192
+ readBridgeAsset("package.json"),
193
+ readBridgeAsset("pnpm-lock.yaml")
194
+ ]);
195
+ cachedBootstrap = {
196
+ harnessId: "deepagents",
197
+ bootstrapDir: BOOTSTRAP_DIR,
198
+ files: [
199
+ { path: `${BOOTSTRAP_DIR}/bridge.mjs`, content: bridge },
200
+ { path: `${BOOTSTRAP_DIR}/package.json`, content: pkg },
201
+ { path: `${BOOTSTRAP_DIR}/pnpm-lock.yaml`, content: lock }
202
+ ],
203
+ commands: [
204
+ { command: `mkdir -p ${BOOTSTRAP_DIR}` },
205
+ { command: installRipgrepCommand() },
206
+ {
207
+ command: `pnpm --dir ${BOOTSTRAP_DIR} install --frozen-lockfile --store-dir ${BOOTSTRAP_DIR}/.pnpm-store`
208
+ }
209
+ ]
210
+ };
211
+ return cachedBootstrap;
212
+ },
213
+ doStart: async (startOpts) => {
214
+ const permissionMode = startOpts.permissionMode;
215
+ const sandboxSession = startOpts.sandboxSession;
216
+ const session = sandboxSession.restricted();
217
+ const sandboxId = sandboxSession.id;
218
+ const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
219
+ const isResume = lifecycleState != null;
220
+ const isContinue = startOpts.continueFrom != null;
221
+ const coords = isResume && typeof lifecycleState?.data === "object" ? lifecycleState.data.bridge : void 0;
222
+ const workDir = startOpts.sessionWorkDir;
223
+ const sessionDataDir = `${sandboxSession.defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;
224
+ const bridgeStateDir = `${sessionDataDir}/bridge`;
225
+ const timeoutMs = settings.startupTimeoutMs ?? 12e4;
226
+ const report = startOpts.observability?.report;
227
+ const onDiagnostic = report ? (frame) => report(
228
+ harnessV1DiagnosticFromBridgeFrame(frame, {
229
+ sessionId: startOpts.sessionId,
230
+ timestamp: Date.now()
231
+ })
232
+ ) : void 0;
233
+ if (coords) {
234
+ try {
235
+ const attachUrl = await sandboxSession.getPortUrl({
236
+ port: coords.port,
237
+ protocol: "ws"
238
+ }) + `?agent_bridge_token=${encodeURIComponent(coords.token)}`;
239
+ const attachChannel = new SandboxChannel({
240
+ connect: () => openWebSocket(attachUrl),
241
+ outboundSchema: outboundMessageSchema,
242
+ initialLastSeenEventId: coords.lastSeenEventId,
243
+ onDiagnostic
244
+ });
245
+ await attachChannel.open(isContinue ? { resume: true } : void 0);
246
+ return createSession({
247
+ sessionId: startOpts.sessionId,
248
+ channel: attachChannel,
249
+ proc: void 0,
250
+ model: settings.model,
251
+ bridgePort: coords.port,
252
+ bridgeToken: coords.token,
253
+ sandboxId,
254
+ isResume: true,
255
+ attached: true,
256
+ permissionMode,
257
+ recursionLimit: settings.recursionLimit
258
+ });
259
+ } catch {
260
+ }
261
+ }
262
+ const port = resolveBridgePort(sandboxSession, settings.port);
263
+ const token = randomBytes(32).toString("hex");
264
+ const skillsPaths = [`${workDir}${SKILLS_SOURCE_PATH}`];
265
+ if ((startOpts.skills?.length ?? 0) > 0) {
266
+ const homeDir = await resolveSandboxHomeDir({
267
+ sandbox: session,
268
+ abortSignal: startOpts.abortSignal
269
+ });
270
+ const homeSkillsRoot = `${homeDir}${SKILLS_SOURCE_PATH}`;
271
+ await writeSkills({
272
+ sandbox: session,
273
+ root: homeSkillsRoot,
274
+ skills: startOpts.skills ?? [],
275
+ abortSignal: startOpts.abortSignal
276
+ });
277
+ skillsPaths.push(homeSkillsRoot);
278
+ }
279
+ const env = {
280
+ ...resolveDeepAgentsEnv({ auth: settings.auth }),
281
+ BRIDGE_CHANNEL_TOKEN: token,
282
+ BRIDGE_WS_PORT: String(port)
283
+ };
284
+ await session.run({
285
+ command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)}`,
286
+ abortSignal: startOpts.abortSignal
287
+ });
288
+ await markBridgeStarting({
289
+ sandbox: session,
290
+ bridgeStateDir,
291
+ bridgeType: "deepagents",
292
+ abortSignal: startOpts.abortSignal
293
+ });
294
+ const proc = await session.spawn({
295
+ command: `node ${BOOTSTRAP_DIR}/bridge.mjs --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)} --bootstrap-dir ${shellQuote(BOOTSTRAP_DIR)}`,
296
+ env,
297
+ abortSignal: startOpts.abortSignal
298
+ });
299
+ const { port: boundPort } = await waitForBridgeReady({
300
+ proc,
301
+ sandbox: session,
302
+ bridgeStateDir,
303
+ bridgeType: "deepagents",
304
+ timeoutMs,
305
+ abortSignal: startOpts.abortSignal,
306
+ createTimeoutError: () => new Error("deepagents bridge did not become ready in time."),
307
+ createExitError: () => new Error("deepagents bridge exited before becoming ready.")
308
+ });
309
+ void forwardBridgeStderr(proc.stderr);
310
+ const wsUrl = await sandboxSession.getPortUrl({
311
+ port: boundPort,
312
+ protocol: "ws"
313
+ }) + `?agent_bridge_token=${encodeURIComponent(token)}`;
314
+ const channel = new SandboxChannel({
315
+ connect: () => openWebSocket(wsUrl),
316
+ outboundSchema: outboundMessageSchema,
317
+ onDiagnostic
318
+ });
319
+ await channel.open();
320
+ return createSession({
321
+ sessionId: startOpts.sessionId,
322
+ channel,
323
+ proc,
324
+ model: settings.model,
325
+ bridgePort: boundPort,
326
+ bridgeToken: token,
327
+ sandboxId,
328
+ isResume,
329
+ // Freshly spawned bridge — it must receive the instructions on the first prompt.
330
+ attached: false,
331
+ skillsPaths,
332
+ permissionMode,
333
+ recursionLimit: settings.recursionLimit
334
+ });
335
+ }
336
+ };
337
+ }
338
+ function resolveBridgePort(sandboxSession, override) {
339
+ if (override !== void 0) return override;
340
+ if (sandboxSession.ports.length > 0) return sandboxSession.ports[0];
341
+ throw new HarnessCapabilityUnsupportedError({
342
+ harnessId: "deepagents",
343
+ message: "The deepagents harness needs a TCP port exposed by the sandbox. Create the sandbox with `ports: [<port>]` or pass `createDeepAgents({ port })`."
344
+ });
345
+ }
346
+ async function readBridgeAsset(name) {
347
+ const candidates = [
348
+ new URL(`./bridge/${name}`, import.meta.url),
349
+ new URL(`../bridge/${name}`, import.meta.url)
350
+ ];
351
+ let lastErr;
352
+ for (const url of candidates) {
353
+ try {
354
+ return await readFile(fileURLToPath(url), "utf8");
355
+ } catch (err) {
356
+ const code = err.code;
357
+ if (code !== "ENOENT") throw err;
358
+ lastErr = err;
359
+ }
360
+ }
361
+ throw lastErr ?? new Error(`bridge asset not found: ${name}`);
362
+ }
363
+ async function resolveSandboxHomeDir({
364
+ sandbox,
365
+ abortSignal
366
+ }) {
367
+ const result = await sandbox.run({
368
+ command: 'printf "%s" "$HOME"',
369
+ abortSignal
370
+ });
371
+ const homeDir = result.stdout.trim();
372
+ if (result.exitCode !== 0 || !homeDir.startsWith("/")) {
373
+ throw new Error(
374
+ `Unable to resolve sandbox HOME directory: ${result.stderr || result.stdout}`
375
+ );
376
+ }
377
+ return homeDir;
378
+ }
379
+ async function writeSkills({
380
+ sandbox,
381
+ root,
382
+ skills,
383
+ abortSignal
384
+ }) {
385
+ for (const skill of skills) {
386
+ const name = safeSkillName(skill.name);
387
+ const skillDir = `${root}/${name}`;
388
+ const content = `---
389
+ name: ${name}
390
+ description: ${skill.description}
391
+ ---
392
+
393
+ ${skill.content}`;
394
+ await sandbox.writeTextFile({
395
+ path: `${skillDir}/SKILL.md`,
396
+ content,
397
+ abortSignal
398
+ });
399
+ for (const file of skill.files ?? []) {
400
+ await sandbox.writeTextFile({
401
+ path: `${skillDir}/${safeSkillFilePath(name, file.path)}`,
402
+ content: file.content,
403
+ abortSignal
404
+ });
405
+ }
406
+ }
407
+ }
408
+ function safeSkillName(name) {
409
+ if (!/^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/.test(name)) {
410
+ throw new Error(
411
+ `Invalid deepagents skill name '${name}': must be lowercase alphanumeric with hyphens, 1-64 chars.`
412
+ );
413
+ }
414
+ return name;
415
+ }
416
+ function safeSkillFilePath(skillName, filePath) {
417
+ const normalized = filePath.replace(/^\/+/, "");
418
+ if (normalized === "" || normalized.startsWith("../") || normalized.includes("/../") || normalized.endsWith("/..")) {
419
+ throw new Error(`Invalid skill file path for '${skillName}': ${filePath}`);
420
+ }
421
+ return normalized;
422
+ }
423
+ function shellQuote(value) {
424
+ return `'${value.replace(/'/g, `'\\''`)}'`;
425
+ }
426
+ function openWebSocket(url) {
427
+ return new Promise((resolve, reject) => {
428
+ const ws = new WebSocket(url);
429
+ const onOpen = () => {
430
+ ws.off("error", onError);
431
+ resolve(ws);
432
+ };
433
+ const onError = (err) => {
434
+ ws.off("open", onOpen);
435
+ reject(err);
436
+ };
437
+ ws.once("open", onOpen);
438
+ ws.once("error", onError);
439
+ });
440
+ }
441
+ async function forwardBridgeStderr(stream) {
442
+ try {
443
+ const reader = stream.pipeThrough(new TextDecoderStream()).getReader();
444
+ while (true) {
445
+ const { value, done } = await reader.read();
446
+ if (done) return;
447
+ if (value) {
448
+ const trimmed = value.endsWith("\n") ? value.slice(0, -1) : value;
449
+ if (trimmed.length > 0) {
450
+ console.log(`[bridge stderr] ${trimmed}`);
451
+ }
452
+ }
453
+ }
454
+ } catch {
455
+ }
456
+ }
457
+ function createSession({
458
+ sessionId,
459
+ channel,
460
+ proc,
461
+ model,
462
+ bridgePort,
463
+ bridgeToken,
464
+ sandboxId,
465
+ isResume,
466
+ attached,
467
+ skillsPaths,
468
+ permissionMode,
469
+ recursionLimit
470
+ }) {
471
+ let stopped = false;
472
+ let instructionsApplied = attached;
473
+ const wireTurn = (turnOpts) => {
474
+ let pendingResolve;
475
+ let pendingReject;
476
+ const done = new Promise((resolve, reject) => {
477
+ pendingResolve = resolve;
478
+ pendingReject = reject;
479
+ });
480
+ const unsubs = [];
481
+ const forward = (event) => {
482
+ try {
483
+ turnOpts.emit(event);
484
+ } catch {
485
+ }
486
+ };
487
+ const eventTypes = [
488
+ "stream-start",
489
+ "text-start",
490
+ "text-delta",
491
+ "text-end",
492
+ "reasoning-start",
493
+ "reasoning-delta",
494
+ "reasoning-end",
495
+ "tool-call",
496
+ "tool-approval-request",
497
+ "tool-result",
498
+ "file-change",
499
+ "finish-step",
500
+ "raw"
501
+ ];
502
+ let isSettled = false;
503
+ const settleSuccess = () => {
504
+ if (isSettled) return;
505
+ isSettled = true;
506
+ for (const u of unsubs) u();
507
+ pendingResolve();
508
+ };
509
+ const settleError = (err) => {
510
+ if (isSettled) return;
511
+ isSettled = true;
512
+ for (const u of unsubs) u();
513
+ pendingReject(err);
514
+ };
515
+ for (const type of eventTypes) {
516
+ unsubs.push(channel.on(type, (msg) => forward(msg)));
517
+ }
518
+ unsubs.push(
519
+ channel.on("finish", (msg) => {
520
+ forward(msg);
521
+ settleSuccess();
522
+ })
523
+ );
524
+ unsubs.push(
525
+ channel.on("error", (msg) => {
526
+ forward(msg);
527
+ settleError(msg.error);
528
+ })
529
+ );
530
+ const onClose = (_code, reason) => {
531
+ if (isSettled) return;
532
+ if (reason === "suspended") {
533
+ settleSuccess();
534
+ return;
535
+ }
536
+ settleError(
537
+ new Error("deepagents bridge closed before the turn finished.")
538
+ );
539
+ };
540
+ channel.onClose(onClose);
541
+ const onAbort = () => {
542
+ if (isSettled) return;
543
+ try {
544
+ channel.send({ type: "abort" });
545
+ } catch {
546
+ }
547
+ settleError(
548
+ turnOpts.abortSignal?.reason ?? new DOMException("Aborted", "AbortError")
549
+ );
550
+ };
551
+ if (turnOpts.abortSignal) {
552
+ if (turnOpts.abortSignal.aborted) {
553
+ onAbort();
554
+ } else {
555
+ turnOpts.abortSignal.addEventListener("abort", onAbort, { once: true });
556
+ }
557
+ }
558
+ return {
559
+ submitToolResult: async (input) => {
560
+ channel.send({
561
+ type: "tool-result",
562
+ toolCallId: input.toolCallId,
563
+ output: input.output,
564
+ isError: input.isError
565
+ });
566
+ },
567
+ submitUserMessage: async (text) => {
568
+ channel.send({ type: "user-message", text });
569
+ },
570
+ submitToolApproval: async (input) => {
571
+ channel.send({
572
+ type: "tool-approval-response",
573
+ approvalId: input.approvalId,
574
+ approved: input.approved,
575
+ ...input.reason != null ? { reason: input.reason } : {}
576
+ });
577
+ },
578
+ done
579
+ };
580
+ };
581
+ const unsupported = (capability) => {
582
+ throw new HarnessCapabilityUnsupportedError({
583
+ harnessId: "deepagents",
584
+ message: `Harness 'deepagents' does not support ${capability} yet.`
585
+ });
586
+ };
587
+ return {
588
+ sessionId,
589
+ isResume,
590
+ modelId: model,
591
+ doPromptTurn: async (promptOpts) => {
592
+ const control = wireTurn({
593
+ emit: promptOpts.emit,
594
+ abortSignal: promptOpts.abortSignal
595
+ });
596
+ const applyInstructions = !instructionsApplied && !!promptOpts.instructions;
597
+ instructionsApplied = true;
598
+ channel.send({
599
+ type: "start",
600
+ prompt: extractUserText(promptOpts.prompt),
601
+ ...applyInstructions ? { instructions: promptOpts.instructions } : {},
602
+ tools: (promptOpts.tools ?? []).map((t) => ({
603
+ name: t.name,
604
+ description: t.description,
605
+ inputSchema: t.inputSchema
606
+ })),
607
+ ...model ? { model } : {},
608
+ ...skillsPaths?.length ? { skillsPaths } : {},
609
+ ...permissionMode ? { permissionMode } : {},
610
+ ...recursionLimit != null ? { recursionLimit } : {}
611
+ });
612
+ return control;
613
+ },
614
+ doContinueTurn: async (continueOpts) => {
615
+ return wireTurn({
616
+ emit: continueOpts.emit,
617
+ abortSignal: continueOpts.abortSignal
618
+ });
619
+ },
620
+ doSuspendTurn: async () => {
621
+ if (stopped) {
622
+ throw new Error(
623
+ `deepagents session ${sessionId} is stopped; cannot suspend.`
624
+ );
625
+ }
626
+ stopped = true;
627
+ const lastSeenEventId = await channel.suspend();
628
+ const payload = {
629
+ type: "continue-turn",
630
+ harnessId: "deepagents",
631
+ specificationVersion: "harness-v1",
632
+ data: {
633
+ bridge: {
634
+ port: bridgePort,
635
+ token: bridgeToken,
636
+ lastSeenEventId,
637
+ sandboxId
638
+ }
639
+ }
640
+ };
641
+ return payload;
642
+ },
643
+ doDetach: async () => {
644
+ if (stopped) {
645
+ throw new Error(
646
+ `deepagents session ${sessionId} is already stopped; cannot detach.`
647
+ );
648
+ }
649
+ stopped = true;
650
+ const lastSeenEventId = await channel.suspend();
651
+ const payload = {
652
+ type: "resume-session",
653
+ harnessId: "deepagents",
654
+ specificationVersion: "harness-v1",
655
+ data: {
656
+ bridge: {
657
+ port: bridgePort,
658
+ token: bridgeToken,
659
+ lastSeenEventId,
660
+ sandboxId
661
+ }
662
+ }
663
+ };
664
+ return payload;
665
+ },
666
+ doCompact: async () => unsupported("manual compaction"),
667
+ doStop: async () => {
668
+ if (stopped) {
669
+ throw new Error(
670
+ `deepagents session ${sessionId} is already stopped; cannot stop.`
671
+ );
672
+ }
673
+ stopped = true;
674
+ await teardown(channel, proc);
675
+ const payload = {
676
+ type: "resume-session",
677
+ harnessId: "deepagents",
678
+ specificationVersion: "harness-v1",
679
+ data: {}
680
+ };
681
+ return payload;
682
+ },
683
+ doDestroy: async () => {
684
+ if (stopped) return;
685
+ stopped = true;
686
+ await teardown(channel, proc);
687
+ }
688
+ };
689
+ }
690
+ async function teardown(channel, proc) {
691
+ channel.beginClose();
692
+ try {
693
+ if (!channel.isClosed()) {
694
+ channel.send({ type: "shutdown" });
695
+ }
696
+ } catch {
697
+ }
698
+ let stopTimer;
699
+ try {
700
+ if (proc) {
701
+ await Promise.race([
702
+ proc.wait(),
703
+ new Promise((resolve) => {
704
+ stopTimer = setTimeout(resolve, 5e3);
705
+ stopTimer.unref?.();
706
+ })
707
+ ]);
708
+ }
709
+ } finally {
710
+ if (stopTimer) clearTimeout(stopTimer);
711
+ try {
712
+ await proc?.kill();
713
+ } catch {
714
+ }
715
+ channel.close();
716
+ }
717
+ }
718
+ function extractUserText(prompt) {
719
+ if (typeof prompt === "string") return prompt;
720
+ const { content } = prompt;
721
+ if (typeof content === "string") return content;
722
+ const parts = [];
723
+ for (const part of content) {
724
+ if (part.type !== "text") {
725
+ throw new HarnessCapabilityUnsupportedError({
726
+ harnessId: "deepagents",
727
+ message: `The deepagents harness does not yet support user message parts of type '${part.type}'. Pass a string or a user message whose content contains only text parts.`
728
+ });
729
+ }
730
+ parts.push(part.text);
731
+ }
732
+ return parts.join("\n\n");
733
+ }
734
+
735
+ // src/index.ts
736
+ var deepAgents = createDeepAgents();
737
+ export {
738
+ createDeepAgents,
739
+ deepAgents
740
+ };
741
+ //# sourceMappingURL=index.js.map