openpond-sdk 0.0.1 → 0.0.3
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/README.md +26 -13
- package/dist/index.js +110 -49
- package/dist/index.js.map +2 -2
- package/dist/types/packages/cloud/src/api/core.d.ts +6 -0
- package/dist/types/packages/cloud/src/api/core.d.ts.map +1 -1
- package/dist/types/packages/cloud/src/sandbox/smoke.d.ts.map +1 -1
- package/dist/types/packages/cloud/src/sandbox/types/client.d.ts +1 -2
- package/dist/types/packages/cloud/src/sandbox/types/client.d.ts.map +1 -1
- package/dist/types/packages/cloud/src/sandbox/types/core.d.ts +0 -1
- package/dist/types/packages/cloud/src/sandbox/types/core.d.ts.map +1 -1
- package/dist/types/packages/cloud/src/sandbox/types/record.d.ts +1 -2
- package/dist/types/packages/cloud/src/sandbox/types/record.d.ts.map +1 -1
- package/dist/types/packages/cloud/src/sandbox/types/runtime-profiles.d.ts +1 -1
- package/dist/types/packages/cloud/src/sandbox/types/runtime-profiles.d.ts.map +1 -1
- package/dist/types/packages/sdk/src/index.d.ts +3 -2
- package/dist/types/packages/sdk/src/index.d.ts.map +1 -1
- package/dist/types/packages/sdk/src/work.d.ts +16 -9
- package/dist/types/packages/sdk/src/work.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,9 +33,33 @@ export async function POST(request: Request) {
|
|
|
33
33
|
}
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
`work.run` creates a sandbox when `sandboxId` is omitted. Pass the returned ID into the next turn to continue in the same filesystem. Use `onEvent` to stream sandbox, model, and
|
|
36
|
+
`work.run` creates a sandbox when `sandboxId` is omitted. Pass the returned ID into the next turn to continue in the same filesystem. Use `onEvent` to stream sandbox, model, command, and output progress to a client.
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Completed files written under `/workspace/outputs` are collected automatically. The model does not need to publish or register them. Each detected file is emitted as an `output` event and returned in `result.outputs`:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const result = await openpond.work.run({
|
|
42
|
+
prompt: "Create a DOCX summary",
|
|
43
|
+
onEvent(event) {
|
|
44
|
+
if (event.type === "output") console.log(event.output.name);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
for (const output of result.outputs) {
|
|
49
|
+
const downloaded = await openpond.work.downloadOutput(
|
|
50
|
+
result.sandboxId,
|
|
51
|
+
output,
|
|
52
|
+
);
|
|
53
|
+
const bytes = Buffer.from(downloaded.file.contentsBase64, "base64");
|
|
54
|
+
// Stream bytes from your authenticated server route.
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Output descriptors include the sandbox path, filename, MIME type, size, modification time, and preview hints. `downloadOutput` remains a separate deterministic file operation, so downloading a result never starts another model turn.
|
|
59
|
+
|
|
60
|
+
If sandbox execution is unavailable, the API fails with the stable `sandbox_runner_unavailable` error instead of returning a successful command result.
|
|
61
|
+
|
|
62
|
+
API failures are exposed as `OpenPondApiError`, with `status` and stable `code` fields for server-side handling. `work.run` propagates these failures instead of asking the model to interpret infrastructure errors.
|
|
39
63
|
|
|
40
64
|
## Raw sandbox API
|
|
41
65
|
|
|
@@ -64,17 +88,6 @@ console.log(result.command.output);
|
|
|
64
88
|
|
|
65
89
|
The package also exports `createOpenPondSandboxClient`, all public sandbox input and response types, and the OpChat helpers used by the Work loop.
|
|
66
90
|
|
|
67
|
-
## Staging
|
|
68
|
-
|
|
69
|
-
Use a server-only environment file while developing:
|
|
70
|
-
|
|
71
|
-
```dotenv
|
|
72
|
-
OPENPOND_API_KEY=opk_...
|
|
73
|
-
OPENPOND_API_URL=https://staging-api.openpond.ai
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
If the staging deployment has Vercel protection enabled, also set `VERCEL_AUTOMATION_BYPASS_SECRET`. The SDK only sends that bypass header to OpenPond staging hosts.
|
|
77
|
-
|
|
78
91
|
## Lifecycle and cleanup
|
|
79
92
|
|
|
80
93
|
Work sandboxes remain available so conversations can continue. Delete them when a conversation is removed or expires:
|
package/dist/index.js
CHANGED
|
@@ -252,12 +252,6 @@ async function runSandboxSmoke(client, options = {}) {
|
|
|
252
252
|
)
|
|
253
253
|
);
|
|
254
254
|
sandboxId = sandbox.id;
|
|
255
|
-
const expectedRuntimeDriver = options.expectedRuntimeDriver ?? "remote-firecracker";
|
|
256
|
-
if (sandbox.runtimeDriver !== expectedRuntimeDriver) {
|
|
257
|
-
throw new Error(
|
|
258
|
-
`expected ${expectedRuntimeDriver}, got ${sandbox.runtimeDriver}`
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
255
|
const expectedMppMode = options.expectedMppMode;
|
|
262
256
|
if (expectedMppMode && sandbox.reservation.mpp?.mode !== expectedMppMode) {
|
|
263
257
|
throw new Error(
|
|
@@ -608,6 +602,19 @@ var ApiResponseTooLargeError = class extends Error {
|
|
|
608
602
|
requestUrl;
|
|
609
603
|
code = "OPENPOND_API_RESPONSE_TOO_LARGE";
|
|
610
604
|
};
|
|
605
|
+
var OpenPondApiError = class extends Error {
|
|
606
|
+
constructor(status, errorCode, label, apiMessage = null) {
|
|
607
|
+
const detail = apiMessage || errorCode;
|
|
608
|
+
super(`${label} failed: ${status}${detail ? ` ${detail}` : ""}`);
|
|
609
|
+
this.status = status;
|
|
610
|
+
this.apiMessage = apiMessage;
|
|
611
|
+
this.name = "OpenPondApiError";
|
|
612
|
+
this.code = errorCode || "OPENPOND_API_ERROR";
|
|
613
|
+
}
|
|
614
|
+
status;
|
|
615
|
+
apiMessage;
|
|
616
|
+
code;
|
|
617
|
+
};
|
|
611
618
|
async function apiFetch(baseUrl, token, requestPath, options = {}) {
|
|
612
619
|
const { timeoutMs = DEFAULT_API_TIMEOUT_MS, maxResponseBytes = DEFAULT_API_RESPONSE_BYTES, ...init } = options;
|
|
613
620
|
const requestUrl = `${baseUrl}${requestPath}`;
|
|
@@ -656,8 +663,9 @@ async function readApiJson(response, label) {
|
|
|
656
663
|
payload = {};
|
|
657
664
|
}
|
|
658
665
|
if (!response.ok) {
|
|
659
|
-
const
|
|
660
|
-
|
|
666
|
+
const errorCode = typeof payload.error === "string" ? payload.error : null;
|
|
667
|
+
const apiMessage = typeof payload.message === "string" ? payload.message : null;
|
|
668
|
+
throw new OpenPondApiError(response.status, errorCode, label, apiMessage);
|
|
661
669
|
}
|
|
662
670
|
return payload;
|
|
663
671
|
}
|
|
@@ -2313,17 +2321,8 @@ async function* streamHostedChatTurn(options) {
|
|
|
2313
2321
|
var DEFAULT_MODEL = "openpond-chat";
|
|
2314
2322
|
var DEFAULT_MAX_STEPS = 24;
|
|
2315
2323
|
var MAX_TOOL_OUTPUT_CHARS = 4e4;
|
|
2316
|
-
var
|
|
2317
|
-
|
|
2318
|
-
super(
|
|
2319
|
-
`Sandbox ${sandboxId} accepted a command without executing it. A real remote runner is required for OpenPond Work.`
|
|
2320
|
-
);
|
|
2321
|
-
this.sandboxId = sandboxId;
|
|
2322
|
-
this.name = "OpenPondNonExecutingSandboxError";
|
|
2323
|
-
}
|
|
2324
|
-
sandboxId;
|
|
2325
|
-
code = "OPENPOND_NON_EXECUTING_SANDBOX";
|
|
2326
|
-
};
|
|
2324
|
+
var WORK_OUTPUT_DIRECTORY = "/workspace/outputs";
|
|
2325
|
+
var MAX_WORK_OUTPUTS = 100;
|
|
2327
2326
|
var OpenPondWorkClient = class {
|
|
2328
2327
|
#apiKey;
|
|
2329
2328
|
#apiBaseUrl;
|
|
@@ -2342,20 +2341,12 @@ var OpenPondWorkClient = class {
|
|
|
2342
2341
|
const emit = async (event) => input.onEvent?.(event);
|
|
2343
2342
|
await emit({ type: "status", message: "Preparing sandbox" });
|
|
2344
2343
|
const sandbox = await this.#resolveSandbox(input);
|
|
2345
|
-
if (sandbox.runtimeDriver === "simulated-firecracker" && !input.allowSimulated) {
|
|
2346
|
-
if (!input.sandboxId) {
|
|
2347
|
-
await this.#sandboxes.delete(sandbox.id, { async: true }).catch(() => void 0);
|
|
2348
|
-
}
|
|
2349
|
-
throw new Error(
|
|
2350
|
-
"OpenPond Work requires a remote-firecracker sandbox, but this environment returned the non-executing simulated-firecracker driver."
|
|
2351
|
-
);
|
|
2352
|
-
}
|
|
2353
2344
|
await emit({
|
|
2354
2345
|
type: "sandbox",
|
|
2355
2346
|
sandboxId: sandbox.id,
|
|
2356
|
-
state: sandbox.state
|
|
2357
|
-
runtimeDriver: sandbox.runtimeDriver
|
|
2347
|
+
state: sandbox.state
|
|
2358
2348
|
});
|
|
2349
|
+
const outputBaseline = await this.#prepareOutputDirectory(sandbox.id);
|
|
2359
2350
|
const messages = [
|
|
2360
2351
|
{ role: "system", content: systemPrompt(sandbox.id) },
|
|
2361
2352
|
...(input.history ?? []).map((message) => ({ ...message })),
|
|
@@ -2394,7 +2385,14 @@ var OpenPondWorkClient = class {
|
|
|
2394
2385
|
await emit({ type: "assistant", text: assistantText });
|
|
2395
2386
|
}
|
|
2396
2387
|
if (toolCalls.length === 0) {
|
|
2397
|
-
const
|
|
2388
|
+
const outputs = await this.#collectOutputs(sandbox.id, outputBaseline);
|
|
2389
|
+
for (const output of outputs) await emit({ type: "output", output });
|
|
2390
|
+
const result = {
|
|
2391
|
+
sandboxId: sandbox.id,
|
|
2392
|
+
text: finalText,
|
|
2393
|
+
steps: step,
|
|
2394
|
+
outputs
|
|
2395
|
+
};
|
|
2398
2396
|
await emit({ type: "done", ...result });
|
|
2399
2397
|
return result;
|
|
2400
2398
|
}
|
|
@@ -2416,6 +2414,33 @@ var OpenPondWorkClient = class {
|
|
|
2416
2414
|
if (sandbox.state === "deleted") return;
|
|
2417
2415
|
await this.#sandboxes.delete(sandboxId, { async: true });
|
|
2418
2416
|
}
|
|
2417
|
+
downloadOutput(sandboxId, output) {
|
|
2418
|
+
return this.#sandboxes.downloadFileResponse(
|
|
2419
|
+
sandboxId,
|
|
2420
|
+
typeof output === "string" ? output : output.path
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
async #prepareOutputDirectory(sandboxId) {
|
|
2424
|
+
await this.#sandboxes.mkdir(sandboxId, {
|
|
2425
|
+
path: WORK_OUTPUT_DIRECTORY,
|
|
2426
|
+
recursive: true
|
|
2427
|
+
});
|
|
2428
|
+
return outputSignatures(await this.#listOutputFiles(sandboxId));
|
|
2429
|
+
}
|
|
2430
|
+
async #collectOutputs(sandboxId, baseline) {
|
|
2431
|
+
const files = await this.#listOutputFiles(sandboxId);
|
|
2432
|
+
return files.filter((file) => baseline.get(file.path) !== outputSignature(file)).sort((left, right) => left.path.localeCompare(right.path)).slice(0, MAX_WORK_OUTPUTS).map(workOutputFromFile);
|
|
2433
|
+
}
|
|
2434
|
+
async #listOutputFiles(sandboxId) {
|
|
2435
|
+
const listed = await this.#sandboxes.listFiles(sandboxId, {
|
|
2436
|
+
path: WORK_OUTPUT_DIRECTORY,
|
|
2437
|
+
recursive: true,
|
|
2438
|
+
maxEntries: 500
|
|
2439
|
+
});
|
|
2440
|
+
return listed.files.filter(
|
|
2441
|
+
(file) => file.type === "file" && !normalizedOutputPath(file.path).includes("/.openpond-")
|
|
2442
|
+
);
|
|
2443
|
+
}
|
|
2419
2444
|
async #resolveSandbox(input) {
|
|
2420
2445
|
if (input.sandboxId) {
|
|
2421
2446
|
const existing = await this.#sandboxes.get(input.sandboxId);
|
|
@@ -2484,18 +2509,6 @@ var OpenPondWorkClient = class {
|
|
|
2484
2509
|
timeoutSeconds: boundedInteger(timeoutSeconds, 1, 900, 180)
|
|
2485
2510
|
});
|
|
2486
2511
|
const output = truncate(response.command.output, MAX_TOOL_OUTPUT_CHARS);
|
|
2487
|
-
if (isNonExecutingSandboxOutput(output)) {
|
|
2488
|
-
await emit({
|
|
2489
|
-
type: "tool",
|
|
2490
|
-
toolCallId,
|
|
2491
|
-
command,
|
|
2492
|
-
status: "failed",
|
|
2493
|
-
output: "Staging accepted the command but did not execute it.",
|
|
2494
|
-
exitCode: response.command.exitCode
|
|
2495
|
-
});
|
|
2496
|
-
await this.#sandboxes.delete(sandboxId, { async: true }).catch(() => void 0);
|
|
2497
|
-
throw new OpenPondNonExecutingSandboxError(sandboxId);
|
|
2498
|
-
}
|
|
2499
2512
|
const status = response.command.status === "succeeded" ? "succeeded" : "failed";
|
|
2500
2513
|
await emit({
|
|
2501
2514
|
type: "tool",
|
|
@@ -2511,7 +2524,7 @@ var OpenPondWorkClient = class {
|
|
|
2511
2524
|
output
|
|
2512
2525
|
});
|
|
2513
2526
|
} catch (error) {
|
|
2514
|
-
if (error instanceof
|
|
2527
|
+
if (error instanceof OpenPondApiError) throw error;
|
|
2515
2528
|
const output = errorMessage(error);
|
|
2516
2529
|
await emit({ type: "tool", toolCallId, command, status: "failed", output });
|
|
2517
2530
|
return toolMessage(toolCallId, { status: "failed", error: output });
|
|
@@ -2540,11 +2553,63 @@ function systemPrompt(sandboxId) {
|
|
|
2540
2553
|
"You are OpenPond Work, a careful coding agent operating in a persistent Linux sandbox.",
|
|
2541
2554
|
`The active sandbox is ${sandboxId}.`,
|
|
2542
2555
|
"Use run_command to inspect the workspace, edit files, and validate your work.",
|
|
2556
|
+
`Place completed user-facing files in ${WORK_OUTPUT_DIRECTORY}. The runtime collects new and revised files there automatically when the turn completes.`,
|
|
2543
2557
|
"Do the requested work completely. Preserve existing user changes and avoid destructive commands.",
|
|
2544
2558
|
"Keep the user informed with concise prose, but call tools whenever verification or file changes are needed.",
|
|
2545
2559
|
"Before finishing, run relevant tests and summarize the concrete result."
|
|
2546
2560
|
].join("\n");
|
|
2547
2561
|
}
|
|
2562
|
+
function outputSignatures(files) {
|
|
2563
|
+
return new Map(files.map((file) => [file.path, outputSignature(file)]));
|
|
2564
|
+
}
|
|
2565
|
+
function outputSignature(file) {
|
|
2566
|
+
return `${file.sizeBytes}:${file.updatedAt}`;
|
|
2567
|
+
}
|
|
2568
|
+
function workOutputFromFile(file) {
|
|
2569
|
+
const name = normalizedOutputPath(file.path).split("/").at(-1) || file.path;
|
|
2570
|
+
const mimeType = workOutputMimeType(name);
|
|
2571
|
+
return {
|
|
2572
|
+
path: file.path,
|
|
2573
|
+
name,
|
|
2574
|
+
mimeType,
|
|
2575
|
+
sizeBytes: file.sizeBytes,
|
|
2576
|
+
updatedAt: file.updatedAt,
|
|
2577
|
+
isBinary: file.isBinary ?? null,
|
|
2578
|
+
previewable: file.previewable ?? (mimeType.startsWith("image/") || mimeType === "application/pdf" || mimeType.startsWith("text/"))
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
function normalizedOutputPath(value) {
|
|
2582
|
+
return value.replaceAll("\\", "/");
|
|
2583
|
+
}
|
|
2584
|
+
function workOutputMimeType(name) {
|
|
2585
|
+
const extension = name.toLowerCase().match(/\.[^.]+$/)?.[0] ?? "";
|
|
2586
|
+
return WORK_OUTPUT_MIME_TYPES[extension] ?? "application/octet-stream";
|
|
2587
|
+
}
|
|
2588
|
+
var WORK_OUTPUT_MIME_TYPES = Object.freeze({
|
|
2589
|
+
".avif": "image/avif",
|
|
2590
|
+
".csv": "text/csv",
|
|
2591
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
2592
|
+
".gif": "image/gif",
|
|
2593
|
+
".html": "text/html",
|
|
2594
|
+
".jpeg": "image/jpeg",
|
|
2595
|
+
".jpg": "image/jpeg",
|
|
2596
|
+
".json": "application/json",
|
|
2597
|
+
".m4a": "audio/mp4",
|
|
2598
|
+
".md": "text/markdown",
|
|
2599
|
+
".mov": "video/quicktime",
|
|
2600
|
+
".mp3": "audio/mpeg",
|
|
2601
|
+
".mp4": "video/mp4",
|
|
2602
|
+
".pdf": "application/pdf",
|
|
2603
|
+
".png": "image/png",
|
|
2604
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
2605
|
+
".svg": "image/svg+xml",
|
|
2606
|
+
".tsv": "text/tab-separated-values",
|
|
2607
|
+
".txt": "text/plain",
|
|
2608
|
+
".wav": "audio/wav",
|
|
2609
|
+
".webm": "video/webm",
|
|
2610
|
+
".webp": "image/webp",
|
|
2611
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
2612
|
+
});
|
|
2548
2613
|
function toolMessage(toolCallId, payload) {
|
|
2549
2614
|
return { role: "tool", tool_call_id: toolCallId, content: JSON.stringify(payload) };
|
|
2550
2615
|
}
|
|
@@ -2560,10 +2625,6 @@ function truncate(value, maximum) {
|
|
|
2560
2625
|
function errorMessage(error) {
|
|
2561
2626
|
return error instanceof Error ? error.message : String(error);
|
|
2562
2627
|
}
|
|
2563
|
-
function isNonExecutingSandboxOutput(output) {
|
|
2564
|
-
const normalized = output.toLowerCase();
|
|
2565
|
-
return normalized.includes("command accepted by simulated-firecracker driver") || normalized.includes("no host command was executed");
|
|
2566
|
-
}
|
|
2567
2628
|
function sleep2(milliseconds, signal) {
|
|
2568
2629
|
return new Promise((resolve, reject) => {
|
|
2569
2630
|
if (signal?.aborted) {
|
|
@@ -2585,7 +2646,7 @@ function sleep2(milliseconds, signal) {
|
|
|
2585
2646
|
|
|
2586
2647
|
// ../cloud/src/sandbox/types/runtime-profiles.ts
|
|
2587
2648
|
var SANDBOX_RUNTIME_PROFILE_IDS = [
|
|
2588
|
-
"openpond-generic-
|
|
2649
|
+
"openpond-generic-v1",
|
|
2589
2650
|
"openpond-work-v1",
|
|
2590
2651
|
"openpond-coding-core-v1",
|
|
2591
2652
|
"openpond-agent-harness-v1"
|
|
@@ -2616,8 +2677,8 @@ function createOpenPondClient(options) {
|
|
|
2616
2677
|
return new OpenPondClient(options);
|
|
2617
2678
|
}
|
|
2618
2679
|
export {
|
|
2680
|
+
OpenPondApiError,
|
|
2619
2681
|
OpenPondClient,
|
|
2620
|
-
OpenPondNonExecutingSandboxError,
|
|
2621
2682
|
OpenPondSandboxClient,
|
|
2622
2683
|
OpenPondWorkClient,
|
|
2623
2684
|
SANDBOX_RUNTIME_PROFILE_IDS,
|