@ai-sdk/harness-deepagents 1.0.70 → 1.0.72

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.
@@ -1,13 +1,10 @@
1
1
  import { randomBytes } from 'node:crypto';
2
- import { readFile } from 'node:fs/promises';
3
2
  import { posix } from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
3
  import {
6
4
  commonTool,
7
5
  HarnessCapabilityUnsupportedError,
8
6
  harnessV1DiagnosticFromBridgeFrame,
9
7
  type HarnessV1,
10
- type HarnessV1Bootstrap,
11
8
  type HarnessV1BuiltinTool,
12
9
  type HarnessV1BuiltinToolFiltering,
13
10
  type HarnessV1ContinueTurnState,
@@ -15,6 +12,7 @@ import {
15
12
  type HarnessV1PermissionMode,
16
13
  type HarnessV1Prompt,
17
14
  type HarnessV1PromptControl,
15
+ type HarnessV1PortEndpoint,
18
16
  type HarnessV1ResumeSessionState,
19
17
  type HarnessV1Session,
20
18
  type HarnessV1Skill,
@@ -26,9 +24,11 @@ import {
26
24
  createBridgeStartupError,
27
25
  drainBridgeProcessStream,
28
26
  forwardBridgeProcessStream,
27
+ maskSandboxCredentials,
29
28
  resolveSandboxHomeDir,
30
29
  SandboxChannel,
31
30
  shellQuote,
31
+ warnCredentialBrokeringUnavailable,
32
32
  waitForBridgeReady,
33
33
  writeSkills as writeHarnessSkills,
34
34
  } from '@ai-sdk/harness/utils';
@@ -36,6 +36,13 @@ import { tool, type Experimental_SandboxProcess } from '@ai-sdk/provider-utils';
36
36
  import { WebSocket } from 'ws';
37
37
  import { z } from 'zod/v4';
38
38
  import {
39
+ DEEPAGENTS_BOOTSTRAP_DIR as BOOTSTRAP_DIR,
40
+ getDeepAgentsBootstrap,
41
+ } from './deepagents-bootstrap';
42
+ import {
43
+ createDeepAgentsRequestTransformations,
44
+ DEEPAGENTS_CREDENTIAL_ENVIRONMENT_VARIABLES,
45
+ resolveDeepAgentsAuthenticationMode,
39
46
  resolveDeepAgentsEnv,
40
47
  type DeepAgentsAuthOptions,
41
48
  } from './deepagents-auth';
@@ -48,12 +55,6 @@ import { VERSION } from './version';
48
55
 
49
56
  type DeepAgentsChannel = SandboxChannel<OutboundMessage, InboundMessage>;
50
57
 
51
- /*
52
- * Bootstrap is derived state stored under the sandbox's default working
53
- * directory so snapshot-capable providers preserve its installation and
54
- * recipe marker without requiring root filesystem access.
55
- */
56
- const BOOTSTRAP_DIR = '.harness-bootstrap/deepagents';
57
58
  /**
58
59
  * Value to use in User-Agent and `x-client-app` headers.
59
60
  */
@@ -73,32 +74,6 @@ export type DeepAgentsThinkingConfig =
73
74
  readonly type: 'disabled';
74
75
  };
75
76
 
76
- // Pinned ripgrep release + per-arch tarball checksums (verified before install).
77
- const RIPGREP_VERSION = '14.1.1';
78
- const RIPGREP_SHA256_X64 =
79
- '4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e';
80
- const RIPGREP_SHA256_ARM =
81
- 'c827481c4ff4ea10c9dc7a4022c8de5db34a5737cb74484d62eb94a95841ab2f';
82
-
83
- // Idempotent, checksum-verified install of a static ripgrep binary into a PATH dir.
84
- // DeepAgents' grep shells out to `rg`; without it, its fallback reads the whole workdir (incl. node_modules) into memory and OOMs. Skipped if `rg` already exists.
85
- function installRipgrepCommand(): string {
86
- const v = RIPGREP_VERSION;
87
- return [
88
- 'command -v rg >/dev/null 2>&1 || {',
89
- 'case "$(uname -m)" in',
90
- `aarch64) a=aarch64-unknown-linux-gnu; sha=${RIPGREP_SHA256_ARM} ;;`,
91
- `*) a=x86_64-unknown-linux-musl; sha=${RIPGREP_SHA256_X64} ;;`,
92
- 'esac;',
93
- `f=/tmp/ripgrep-${v}.tar.gz;`,
94
- `curl -fsSL "https://github.com/BurntSushi/ripgrep/releases/download/${v}/ripgrep-${v}-$a.tar.gz" -o "$f"`,
95
- '&& echo "$sha $f" | sha256sum -c -',
96
- '&& tar xzf "$f" -C /tmp',
97
- `&& mv "/tmp/ripgrep-${v}-$a/rg" /usr/local/bin/rg && chmod +x /usr/local/bin/rg;`,
98
- '}',
99
- ].join(' ');
100
- }
101
-
102
77
  // Skills source subpath, written under $HOME (out of the work dir so it can't clash with code cloned into the work dir) and also discovered from <workDir> for repo-provided skills.
103
78
  const SKILLS_SOURCE_PATH = '/.agents/skills';
104
79
 
@@ -212,8 +187,6 @@ type DeepAgentsBridgeCoords = z.infer<typeof deepAgentsBridgeCoordsSchema>;
212
187
  export function createDeepAgents(
213
188
  settings: DeepAgentsHarnessSettings = {},
214
189
  ): HarnessV1<typeof DEEPAGENTS_BUILTIN_TOOLS> {
215
- let cachedBootstrap: HarnessV1Bootstrap | undefined;
216
-
217
190
  return {
218
191
  specificationVersion: 'harness-v1',
219
192
  harnessId: 'deepagents',
@@ -221,33 +194,35 @@ export function createDeepAgents(
221
194
  // Built-in tool approvals are gated in-bridge via DeepAgents' interruptOn (HITL) middleware.
222
195
  supportsBuiltinToolApprovals: true,
223
196
  lifecycleStateSchema: deepAgentsResumeStateSchema,
224
- getBootstrap: async () => {
225
- if (cachedBootstrap != null) return cachedBootstrap;
226
- const [bridge, pkg, lock] = await Promise.all([
227
- readBridgeAsset('index.mjs'),
228
- readBridgeAsset('package.json'),
229
- readBridgeAsset('pnpm-lock.yaml'),
230
- ]);
231
- cachedBootstrap = {
232
- harnessId: 'deepagents',
233
- bootstrapDir: BOOTSTRAP_DIR,
234
- files: [
235
- { path: `${BOOTSTRAP_DIR}/bridge.mjs`, content: bridge },
236
- { path: `${BOOTSTRAP_DIR}/package.json`, content: pkg },
237
- { path: `${BOOTSTRAP_DIR}/pnpm-lock.yaml`, content: lock },
238
- ],
239
- commands: [
240
- { command: installRipgrepCommand() },
241
- {
242
- command: 'pnpm install --frozen-lockfile --store-dir .pnpm-store',
243
- },
244
- ],
245
- };
246
- return cachedBootstrap;
247
- },
197
+ getBootstrap: getDeepAgentsBootstrap,
248
198
  doStart: async startOpts => {
249
199
  const permissionMode = startOpts.permissionMode;
250
200
  const sandboxSession = startOpts.sandboxSession;
201
+ const authenticationMode = resolveDeepAgentsAuthenticationMode({
202
+ auth: settings.auth,
203
+ });
204
+ const resolvedAuthEnvironment = resolveDeepAgentsEnv({
205
+ auth: settings.auth,
206
+ });
207
+ let sandboxAuthEnvironment = resolvedAuthEnvironment;
208
+ if (sandboxSession.addRequestTransformations != null) {
209
+ const requestTransformations = createDeepAgentsRequestTransformations(
210
+ resolvedAuthEnvironment,
211
+ authenticationMode,
212
+ );
213
+ if (requestTransformations.length > 0) {
214
+ await sandboxSession.addRequestTransformations(
215
+ requestTransformations,
216
+ );
217
+ }
218
+ sandboxAuthEnvironment = maskSandboxCredentials({
219
+ environment: resolvedAuthEnvironment,
220
+ credentialEnvironmentVariables:
221
+ DEEPAGENTS_CREDENTIAL_ENVIRONMENT_VARIABLES,
222
+ });
223
+ } else {
224
+ warnCredentialBrokeringUnavailable();
225
+ }
251
226
  const session = sandboxSession.restricted();
252
227
  const sandboxId = sandboxSession.id;
253
228
  const bootstrapDir = posix.resolve(
@@ -286,13 +261,16 @@ export function createDeepAgents(
286
261
  // Attach to the still-running bridge (continueFrom replays past the cursor); on failure fall through to a fresh spawn.
287
262
  if (coords) {
288
263
  try {
289
- const attachUrl =
290
- (await sandboxSession.getPortUrl({
291
- port: coords.port,
292
- protocol: 'ws',
293
- })) + `?agent_bridge_token=${encodeURIComponent(coords.token)}`;
264
+ const endpoint = await sandboxSession.getPortEndpoint({
265
+ port: coords.port,
266
+ protocol: 'ws',
267
+ });
268
+ const attachEndpoint = withBridgeToken({
269
+ endpoint,
270
+ token: coords.token,
271
+ });
294
272
  const attachChannel: DeepAgentsChannel = new SandboxChannel({
295
- connect: () => openWebSocket(attachUrl),
273
+ connect: () => openWebSocket(attachEndpoint),
296
274
  outboundSchema: outboundMessageSchema,
297
275
  initialLastSeenEventId: coords.lastSeenEventId,
298
276
  onDiagnostic,
@@ -347,7 +325,7 @@ export function createDeepAgents(
347
325
  }
348
326
 
349
327
  const env = {
350
- ...resolveDeepAgentsEnv({ auth: settings.auth }),
328
+ ...sandboxAuthEnvironment,
351
329
  AI_SDK_HARNESS_CLIENT_APP: DEEPAGENTS_CLIENT_APP,
352
330
  BRIDGE_CHANNEL_TOKEN: token,
353
331
  BRIDGE_WS_PORT: String(port),
@@ -404,14 +382,14 @@ export function createDeepAgents(
404
382
  });
405
383
  void drainBridgeProcessStream(proc.stdout);
406
384
 
407
- const wsUrl =
408
- (await sandboxSession.getPortUrl({
409
- port: boundPort,
410
- protocol: 'ws',
411
- })) + `?agent_bridge_token=${encodeURIComponent(token)}`;
385
+ const endpoint = await sandboxSession.getPortEndpoint({
386
+ port: boundPort,
387
+ protocol: 'ws',
388
+ });
389
+ const bridgeEndpoint = withBridgeToken({ endpoint, token });
412
390
 
413
391
  const channel: DeepAgentsChannel = new SandboxChannel({
414
- connect: () => openWebSocket(wsUrl),
392
+ connect: () => openWebSocket(bridgeEndpoint),
415
393
  outboundSchema: outboundMessageSchema,
416
394
  onDiagnostic,
417
395
  onBridgeError,
@@ -455,24 +433,6 @@ function resolveBridgePort(
455
433
  });
456
434
  }
457
435
 
458
- async function readBridgeAsset(name: string): Promise<string> {
459
- const candidates = [
460
- new URL(`./bridge/${name}`, import.meta.url),
461
- new URL(`../bridge/${name}`, import.meta.url),
462
- ];
463
- let lastErr: unknown;
464
- for (const url of candidates) {
465
- try {
466
- return await readFile(fileURLToPath(url), 'utf8');
467
- } catch (err) {
468
- const code = (err as NodeJS.ErrnoException).code;
469
- if (code !== 'ENOENT') throw err;
470
- lastErr = err;
471
- }
472
- }
473
- throw lastErr ?? new Error(`bridge asset not found: ${name}`);
474
- }
475
-
476
436
  // Materialize each skill as a native deepagents `<name>/SKILL.md` folder (+ attached files) under the given root, so skills load on demand and file references resolve.
477
437
  async function writeSkills({
478
438
  sandbox,
@@ -503,9 +463,14 @@ async function writeSkills({
503
463
  });
504
464
  }
505
465
 
506
- function openWebSocket(url: string): Promise<WebSocket> {
466
+ function openWebSocket({
467
+ url,
468
+ headers,
469
+ }: HarnessV1PortEndpoint): Promise<WebSocket> {
507
470
  return new Promise((resolve, reject) => {
508
- const ws = new WebSocket(url);
471
+ const ws = new WebSocket(url, {
472
+ headers: headers == null ? undefined : { ...headers },
473
+ });
509
474
  const onOpen = () => {
510
475
  ws.off('error', onError);
511
476
  resolve(ws);
@@ -519,6 +484,18 @@ function openWebSocket(url: string): Promise<WebSocket> {
519
484
  });
520
485
  }
521
486
 
487
+ function withBridgeToken({
488
+ endpoint,
489
+ token,
490
+ }: {
491
+ endpoint: HarnessV1PortEndpoint;
492
+ token: string;
493
+ }): HarnessV1PortEndpoint {
494
+ const bridgeUrl = new URL(endpoint.url);
495
+ bridgeUrl.searchParams.set('agent_bridge_token', token);
496
+ return { ...endpoint, url: bridgeUrl.toString() };
497
+ }
498
+
522
499
  function createSession({
523
500
  sessionId,
524
501
  channel,
package/src/index.ts CHANGED
@@ -9,4 +9,7 @@ export type {
9
9
  DeepAgentsHarnessSettings,
10
10
  DeepAgentsThinkingConfig,
11
11
  } from './deepagents-harness';
12
- export type { DeepAgentsAuthOptions } from './deepagents-auth';
12
+ export type {
13
+ DeepAgentsAuthenticationMode,
14
+ DeepAgentsAuthOptions,
15
+ } from './deepagents-auth';