openpond-sdk 0.0.2 → 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 +23 -12
- package/dist/index.js +90 -1
- package/dist/index.js.map +2 -2
- package/dist/types/packages/sdk/src/index.d.ts +1 -1
- package/dist/types/packages/sdk/src/index.d.ts.map +1 -1
- package/dist/types/packages/sdk/src/work.d.ts +16 -1
- package/dist/types/packages/sdk/src/work.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,29 @@ 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
|
+
|
|
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.
|
|
37
59
|
|
|
38
60
|
If sandbox execution is unavailable, the API fails with the stable `sandbox_runner_unavailable` error instead of returning a successful command result.
|
|
39
61
|
|
|
@@ -66,17 +88,6 @@ console.log(result.command.output);
|
|
|
66
88
|
|
|
67
89
|
The package also exports `createOpenPondSandboxClient`, all public sandbox input and response types, and the OpChat helpers used by the Work loop.
|
|
68
90
|
|
|
69
|
-
## Staging
|
|
70
|
-
|
|
71
|
-
Use a server-only environment file while developing:
|
|
72
|
-
|
|
73
|
-
```dotenv
|
|
74
|
-
OPENPOND_API_KEY=opk_...
|
|
75
|
-
OPENPOND_API_URL=https://staging-api.openpond.ai
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
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.
|
|
79
|
-
|
|
80
91
|
## Lifecycle and cleanup
|
|
81
92
|
|
|
82
93
|
Work sandboxes remain available so conversations can continue. Delete them when a conversation is removed or expires:
|
package/dist/index.js
CHANGED
|
@@ -2321,6 +2321,8 @@ async function* streamHostedChatTurn(options) {
|
|
|
2321
2321
|
var DEFAULT_MODEL = "openpond-chat";
|
|
2322
2322
|
var DEFAULT_MAX_STEPS = 24;
|
|
2323
2323
|
var MAX_TOOL_OUTPUT_CHARS = 4e4;
|
|
2324
|
+
var WORK_OUTPUT_DIRECTORY = "/workspace/outputs";
|
|
2325
|
+
var MAX_WORK_OUTPUTS = 100;
|
|
2324
2326
|
var OpenPondWorkClient = class {
|
|
2325
2327
|
#apiKey;
|
|
2326
2328
|
#apiBaseUrl;
|
|
@@ -2344,6 +2346,7 @@ var OpenPondWorkClient = class {
|
|
|
2344
2346
|
sandboxId: sandbox.id,
|
|
2345
2347
|
state: sandbox.state
|
|
2346
2348
|
});
|
|
2349
|
+
const outputBaseline = await this.#prepareOutputDirectory(sandbox.id);
|
|
2347
2350
|
const messages = [
|
|
2348
2351
|
{ role: "system", content: systemPrompt(sandbox.id) },
|
|
2349
2352
|
...(input.history ?? []).map((message) => ({ ...message })),
|
|
@@ -2382,7 +2385,14 @@ var OpenPondWorkClient = class {
|
|
|
2382
2385
|
await emit({ type: "assistant", text: assistantText });
|
|
2383
2386
|
}
|
|
2384
2387
|
if (toolCalls.length === 0) {
|
|
2385
|
-
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
|
+
};
|
|
2386
2396
|
await emit({ type: "done", ...result });
|
|
2387
2397
|
return result;
|
|
2388
2398
|
}
|
|
@@ -2404,6 +2414,33 @@ var OpenPondWorkClient = class {
|
|
|
2404
2414
|
if (sandbox.state === "deleted") return;
|
|
2405
2415
|
await this.#sandboxes.delete(sandboxId, { async: true });
|
|
2406
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
|
+
}
|
|
2407
2444
|
async #resolveSandbox(input) {
|
|
2408
2445
|
if (input.sandboxId) {
|
|
2409
2446
|
const existing = await this.#sandboxes.get(input.sandboxId);
|
|
@@ -2516,11 +2553,63 @@ function systemPrompt(sandboxId) {
|
|
|
2516
2553
|
"You are OpenPond Work, a careful coding agent operating in a persistent Linux sandbox.",
|
|
2517
2554
|
`The active sandbox is ${sandboxId}.`,
|
|
2518
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.`,
|
|
2519
2557
|
"Do the requested work completely. Preserve existing user changes and avoid destructive commands.",
|
|
2520
2558
|
"Keep the user informed with concise prose, but call tools whenever verification or file changes are needed.",
|
|
2521
2559
|
"Before finishing, run relevant tests and summarize the concrete result."
|
|
2522
2560
|
].join("\n");
|
|
2523
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
|
+
});
|
|
2524
2613
|
function toolMessage(toolCallId, payload) {
|
|
2525
2614
|
return { role: "tool", tool_call_id: toolCallId, content: JSON.stringify(payload) };
|
|
2526
2615
|
}
|