@openagentsinc/ai-sdk-sandbox-local 0.2.0-rc.6 → 0.2.1-rc.2

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 (2) hide show
  1. package/package.json +3 -2
  2. package/src/index.test.ts +0 -276
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@openagentsinc/ai-sdk-sandbox-local",
3
- "version": "0.2.0-rc.6",
3
+ "version": "0.2.1-rc.2",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "files": [
7
- "src",
7
+ "src/**/*.ts",
8
+ "!src/**/*.test.ts",
8
9
  "README.md"
9
10
  ],
10
11
  "type": "module",
package/src/index.test.ts DELETED
@@ -1,276 +0,0 @@
1
- import { mkdtemp, rm } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import { resolve } from "node:path";
4
- import { afterEach, describe, expect, test } from "vite-plus/test";
5
- import { HarnessAgent } from "@ai-sdk/harness/agent";
6
- import type {
7
- HarnessV1,
8
- HarnessV1ContinueTurnOptions,
9
- HarnessV1LifecycleState,
10
- HarnessV1Prompt,
11
- HarnessV1PromptTurnOptions,
12
- HarnessV1Session,
13
- HarnessV1StartOptions,
14
- HarnessV1StreamPart,
15
- } from "@ai-sdk/harness";
16
- import { createLocalAiSdkSandboxProvider } from "./index.js";
17
-
18
- const roots: string[] = [];
19
-
20
- afterEach(async () => {
21
- await Promise.all(
22
- roots.splice(0).map((root) =>
23
- rm(root, {
24
- force: true,
25
- recursive: true,
26
- }),
27
- ),
28
- );
29
- });
30
-
31
- describe("createLocalAiSdkSandboxProvider", () => {
32
- test("runs a public HarnessAgent.stream fixture without Vercel", async () => {
33
- const root = await mkdtemp(resolve(tmpdir(), "openagents-local-provider-"));
34
- roots.push(root);
35
- const provider = createLocalAiSdkSandboxProvider({
36
- defaultPorts: [4321],
37
- rootDirectory: root,
38
- });
39
- const agent = new HarnessAgent({
40
- harness: fixtureHarness,
41
- sandbox: provider,
42
- sandboxConfig: {
43
- onSession: async ({ session, sessionWorkDir }) => {
44
- await session.writeTextFile({
45
- content: "public fixture\n",
46
- path: `${sessionWorkDir}/README.md`,
47
- });
48
- },
49
- },
50
- });
51
- const session = await agent.createSession({
52
- sessionId: "fixture-session",
53
- });
54
-
55
- try {
56
- const result = await agent.stream({
57
- prompt: "repair the public fixture",
58
- session,
59
- });
60
- let text = "";
61
- for await (const delta of result.textStream) {
62
- text += delta;
63
- }
64
- expect(text).toContain("public fixture repaired");
65
-
66
- const sandbox = await provider.resumeSession?.({
67
- sessionId: "fixture-session",
68
- });
69
- expect(sandbox).toBeDefined();
70
- if (sandbox === undefined) throw new Error("Missing resumed sandbox.");
71
- expect(sandbox.defaultWorkingDirectory).toStartWith(root);
72
- expect(await sandbox.getPortUrl({ port: 4321, protocol: "ws" })).toBe("ws://127.0.0.1:4321/");
73
- expect(
74
- await sandbox.readTextFile({
75
- path: `${sandbox.defaultWorkingDirectory}/codex-fixture-session/result.txt`,
76
- }),
77
- ).toBe("repair the public fixture\n");
78
- const env = await sandbox.run({
79
- command: 'printf \'%s\\n%s\\n%s\' "$HOME" "$CODEX_HOME" "$CLAUDE_CONFIG_DIR"',
80
- });
81
- expect(env.stdout).toContain(`${sandbox.defaultWorkingDirectory}/.openagents-home`);
82
- expect(env.stdout).toContain("/codex");
83
- expect(env.stdout).toContain("/claude-code");
84
- await expect(
85
- sandbox.readTextFile({ path: resolve(tmpdir(), "outside-openagents.txt") }),
86
- ).rejects.toThrow("escapes local sandbox");
87
- const bootstrapFile = "/tmp/harness/local-provider-test.txt";
88
- await sandbox.writeTextFile({ content: "bootstrap\n", path: bootstrapFile });
89
- expect(await sandbox.readTextFile({ path: bootstrapFile })).toBe("bootstrap\n");
90
- await rm(bootstrapFile, { force: true });
91
- } finally {
92
- await session.destroy();
93
- }
94
- });
95
-
96
- test("restricted view keeps infra controls off the tool-safe surface", async () => {
97
- const root = await mkdtemp(resolve(tmpdir(), "openagents-local-provider-"));
98
- roots.push(root);
99
- const provider = createLocalAiSdkSandboxProvider({ rootDirectory: root });
100
- const sandbox = await provider.createSession({ sessionId: "restricted" });
101
-
102
- try {
103
- const restricted = sandbox.restricted();
104
- await restricted.writeTextFile({
105
- content: "a\nb\nc\n",
106
- path: "notes.txt",
107
- });
108
- expect(
109
- await restricted.readTextFile({
110
- endLine: 2,
111
- path: "notes.txt",
112
- startLine: 2,
113
- }),
114
- ).toBe("b");
115
- expect("stop" in restricted).toBe(false);
116
- expect("setNetworkPolicy" in restricted).toBe(false);
117
- await expect(restricted.readTextFile({ path: "../outside.txt" })).rejects.toThrow(
118
- "escapes local sandbox",
119
- );
120
- } finally {
121
- await sandbox.destroy?.();
122
- }
123
- });
124
-
125
- test("can preserve the host Claude configuration lookup", async () => {
126
- const root = await mkdtemp(resolve(tmpdir(), "openagents-local-provider-"));
127
- roots.push(root);
128
- const provider = createLocalAiSdkSandboxProvider({
129
- env: { CLAUDE_CONFIG_DIR: "" },
130
- inheritClaudeConfig: true,
131
- rootDirectory: root,
132
- });
133
- const sandbox = await provider.createSession({ sessionId: "claude-auth" });
134
-
135
- try {
136
- const result = await sandbox.run({
137
- command: "node -p 'JSON.stringify(process.env.CLAUDE_CONFIG_DIR)'",
138
- });
139
- expect(result.stdout.trim()).toBe('\"\"');
140
- expect(sandbox.description).toContain("CLAUDE_CONFIG_DIR: inherited from host");
141
- } finally {
142
- await sandbox.destroy?.();
143
- }
144
- });
145
-
146
- test("resolves an ephemeral bridge port when port zero is exposed", async () => {
147
- const root = await mkdtemp(resolve(tmpdir(), "openagents-local-provider-"));
148
- roots.push(root);
149
- const provider = createLocalAiSdkSandboxProvider({
150
- defaultPorts: [0],
151
- rootDirectory: root,
152
- });
153
- const sandbox = await provider.createSession({ sessionId: "ephemeral-port" });
154
-
155
- try {
156
- expect(await sandbox.getPortUrl({ port: 54321, protocol: "ws" })).toBe(
157
- "ws://127.0.0.1:54321/",
158
- );
159
- } finally {
160
- await sandbox.destroy?.();
161
- }
162
- });
163
- });
164
-
165
- const fixtureHarness: HarnessV1 = {
166
- builtinTools: {},
167
- harnessId: "codex",
168
- specificationVersion: "harness-v1",
169
- doStart: async (options) => makeFixtureSession(options),
170
- };
171
-
172
- function makeFixtureSession(options: HarnessV1StartOptions): HarnessV1Session {
173
- const lifecycle = (): HarnessV1LifecycleState => ({
174
- data: { sessionId: options.sessionId },
175
- harnessId: "codex",
176
- specificationVersion: "harness-v1",
177
- type: "resume-session",
178
- });
179
- const promptTurn = async (turn: HarnessV1PromptTurnOptions) => {
180
- const text = promptToText(turn.prompt);
181
- await options.sandboxSession.restricted().writeTextFile({
182
- content: `${text}\n`,
183
- path: `${options.sessionWorkDir}/result.txt`,
184
- });
185
- turn.emit({ type: "stream-start", modelId: "fixture-model" });
186
- turn.emit({ type: "text-start", id: "text-1" });
187
- turn.emit({
188
- delta: `public fixture repaired: ${text}`,
189
- id: "text-1",
190
- type: "text-delta",
191
- });
192
- turn.emit({ type: "text-end", id: "text-1" });
193
- turn.emit({
194
- finishReason: stopFinishReason,
195
- type: "finish-step",
196
- usage: fixtureUsage,
197
- });
198
- turn.emit({
199
- finishReason: stopFinishReason,
200
- totalUsage: fixtureUsage,
201
- type: "finish",
202
- });
203
- return {
204
- done: Promise.resolve(),
205
- submitToolResult: async () => {},
206
- };
207
- };
208
- const continueTurn = async (turn: HarnessV1ContinueTurnOptions) => {
209
- turn.emit({ type: "stream-start", modelId: "fixture-model" });
210
- turn.emit({
211
- finishReason: stopFinishReason,
212
- totalUsage: zeroUsage,
213
- type: "finish",
214
- });
215
- return {
216
- done: Promise.resolve(),
217
- submitToolResult: async () => {},
218
- };
219
- };
220
- return {
221
- doCompact: async () => {},
222
- doContinueTurn: continueTurn,
223
- doDestroy: async () => {},
224
- doDetach: async () =>
225
- lifecycle() as Extract<HarnessV1LifecycleState, { type: "resume-session" }>,
226
- doPromptTurn: promptTurn,
227
- doStop: async () => lifecycle() as Extract<HarnessV1LifecycleState, { type: "resume-session" }>,
228
- doSuspendTurn: async () => ({
229
- data: { sessionId: options.sessionId },
230
- harnessId: "codex",
231
- specificationVersion: "harness-v1",
232
- type: "continue-turn",
233
- }),
234
- isResume: options.resumeFrom !== undefined || options.continueFrom !== undefined,
235
- sessionId: options.sessionId,
236
- };
237
- }
238
-
239
- function promptToText(prompt: HarnessV1Prompt): string {
240
- if (typeof prompt === "string") return prompt;
241
- if (typeof prompt.content === "string") return prompt.content;
242
- return prompt.content.map((part) => (part.type === "text" ? part.text : "")).join("");
243
- }
244
-
245
- const stopFinishReason: Extract<HarnessV1StreamPart, { type: "finish" }>["finishReason"] = {
246
- raw: undefined,
247
- unified: "stop",
248
- };
249
-
250
- const fixtureUsage: Extract<HarnessV1StreamPart, { type: "finish" }>["totalUsage"] = {
251
- inputTokens: {
252
- cacheRead: undefined,
253
- cacheWrite: undefined,
254
- noCache: 1,
255
- total: 1,
256
- },
257
- outputTokens: {
258
- reasoning: undefined,
259
- text: 1,
260
- total: 1,
261
- },
262
- };
263
-
264
- const zeroUsage: Extract<HarnessV1StreamPart, { type: "finish" }>["totalUsage"] = {
265
- inputTokens: {
266
- cacheRead: undefined,
267
- cacheWrite: undefined,
268
- noCache: undefined,
269
- total: undefined,
270
- },
271
- outputTokens: {
272
- reasoning: undefined,
273
- text: undefined,
274
- total: undefined,
275
- },
276
- };