openpond-sdk 0.6.1 → 0.6.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 (47) hide show
  1. package/README.md +15 -0
  2. package/dist/{chunk-APHPXQEA.js → chunk-22RMONJA.js} +3 -3
  3. package/dist/chunk-22RMONJA.js.map +7 -0
  4. package/dist/{chunk-QLEJOCQX.js → chunk-6T23L3LH.js} +2 -2
  5. package/dist/{chunk-27ETSPHQ.js → chunk-FJ7IMEG5.js} +2 -2
  6. package/dist/{chunk-7O5X7DBU.js → chunk-JHKNPQD5.js} +2 -2
  7. package/dist/index.js +132 -40
  8. package/dist/index.js.map +3 -3
  9. package/dist/profile-actions.js +2 -2
  10. package/dist/project-actions.js +2 -2
  11. package/dist/types/packages/cloud/src/api/core.d.ts +1 -0
  12. package/dist/types/packages/cloud/src/api/core.d.ts.map +1 -1
  13. package/dist/types/packages/cloud/src/sandbox/sandbox-instance-client.d.ts +2 -1
  14. package/dist/types/packages/cloud/src/sandbox/sandbox-instance-client.d.ts.map +1 -1
  15. package/dist/types/packages/cloud/src/sandbox/types/client.d.ts +2 -0
  16. package/dist/types/packages/cloud/src/sandbox/types/client.d.ts.map +1 -1
  17. package/dist/types/packages/sdk/src/index.d.ts +6 -6
  18. package/dist/types/packages/sdk/src/index.d.ts.map +1 -1
  19. package/dist/types/packages/sdk/src/model-batch-review-preparation.d.ts +1 -1
  20. package/dist/types/packages/sdk/src/model-starter-catalog.d.ts +10 -10
  21. package/dist/types/packages/sdk/src/model-starters.d.ts +8 -8
  22. package/dist/types/packages/sdk/src/taskset-authored-contracts.d.ts +2 -2
  23. package/dist/types/packages/sdk/src/taskset-authored-files.d.ts +2 -2
  24. package/dist/types/packages/sdk/src/taskset-draft-api-contracts.d.ts +4 -4
  25. package/dist/types/packages/sdk/src/taskset-draft-client.d.ts +12 -12
  26. package/dist/types/packages/sdk/src/taskset-draft-core.d.ts +3 -3
  27. package/dist/types/packages/sdk/src/taskset-draft-document.d.ts +2 -2
  28. package/dist/types/packages/sdk/src/taskset-draft-package-compiler.d.ts +8 -8
  29. package/dist/types/packages/sdk/src/taskset-draft-source.d.ts +4 -4
  30. package/dist/types/packages/sdk/src/taskset-draft-workspace.d.ts +4 -4
  31. package/dist/types/packages/sdk/src/taskset-package-grader-projection.d.ts +1 -1
  32. package/dist/types/packages/sdk/src/taskset-package-projection.d.ts +10 -10
  33. package/dist/types/packages/sdk/src/training-learning-batch.d.ts +6 -6
  34. package/dist/types/packages/sdk/src/types.d.ts +17 -1
  35. package/dist/types/packages/sdk/src/types.d.ts.map +1 -1
  36. package/dist/types/packages/sdk/src/work-model.d.ts +17 -0
  37. package/dist/types/packages/sdk/src/work-model.d.ts.map +1 -0
  38. package/dist/types/packages/sdk/src/work-sandbox.d.ts +17 -0
  39. package/dist/types/packages/sdk/src/work-sandbox.d.ts.map +1 -0
  40. package/dist/types/packages/sdk/src/work.d.ts +8 -2
  41. package/dist/types/packages/sdk/src/work.d.ts.map +1 -1
  42. package/dist/workflows.js +2 -2
  43. package/package.json +1 -1
  44. package/dist/chunk-APHPXQEA.js.map +0 -7
  45. /package/dist/{chunk-QLEJOCQX.js.map → chunk-6T23L3LH.js.map} +0 -0
  46. /package/dist/{chunk-27ETSPHQ.js.map → chunk-FJ7IMEG5.js.map} +0 -0
  47. /package/dist/{chunk-7O5X7DBU.js.map → chunk-JHKNPQD5.js.map} +0 -0
package/README.md CHANGED
@@ -16,6 +16,21 @@ Node.js 22.14 or newer is required. This package is server-only: never expose an
16
16
 
17
17
  ## Next.js route handler
18
18
 
19
+ Sandbox and model configuration are independently optional. With only `apiKey`,
20
+ Work uses OpenPond's hosted defaults. To run tools in your installed runtime while
21
+ keeping the hosted model, add `sandbox: { endpoint, apiKey }`. To choose a model,
22
+ add `model: { endpoint, apiKey, model }`, where `endpoint` is an OpenAI-compatible
23
+ Chat Completions base URL. Each override requires its own credential; credentials
24
+ are never borrowed from another service. Both overrides together allow omitting
25
+ the hosted `apiKey` for Work.
26
+
27
+ The customer runtime uses fresh guests for subsequent turns. Persist history and
28
+ outputs in your application, then pass them into the next run. A failed output
29
+ save retains the guest for recovery; retry persistence before deleting it. Hosted
30
+ repository provisioning and spending budgets do not apply to a custom runtime.
31
+ An optional UUID `requestId` lets an application reconcile interrupted allocations
32
+ through sandbox metadata; it does not replay model or tool operations.
33
+
19
34
  ```ts
20
35
  // app/api/work/route.ts
21
36
  import { createOpenPondClient } from "openpond-sdk";
@@ -38,11 +38,11 @@ var OpenPondApiError = class extends Error {
38
38
  }
39
39
  };
40
40
  async function apiFetch(baseUrl, token, requestPath, options = {}) {
41
- const { timeoutMs = DEFAULT_API_TIMEOUT_MS, maxResponseBytes = DEFAULT_API_RESPONSE_BYTES, ...init } = options;
41
+ const { timeoutMs = DEFAULT_API_TIMEOUT_MS, maxResponseBytes = DEFAULT_API_RESPONSE_BYTES, useEnvironmentApiKey = true, ...init } = options;
42
42
  const requestUrl = `${baseUrl}${requestPath}`;
43
43
  const headers = new Headers(init.headers);
44
44
  headers.set("Content-Type", "application/json");
45
- const apiKey = process.env.OPENPOND_API_KEY;
45
+ const apiKey = useEnvironmentApiKey ? process.env.OPENPOND_API_KEY : void 0;
46
46
  const trimmedToken = token?.trim() || "";
47
47
  const tokenIsApiKey = trimmedToken.startsWith("opk_");
48
48
  const effectiveApiKey = apiKey || (tokenIsApiKey ? trimmedToken : null);
@@ -152,4 +152,4 @@ export {
152
152
  apiFetch,
153
153
  readApiJson
154
154
  };
155
- //# sourceMappingURL=chunk-APHPXQEA.js.map
155
+ //# sourceMappingURL=chunk-22RMONJA.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../cloud/src/api/core.ts"],
4
+ "sourcesContent": ["const DEFAULT_API_TIMEOUT_MS = 30_000;\nconst DEFAULT_API_RESPONSE_BYTES = 8 * 1024 * 1024;\nexport const LONG_STREAM_API_OPTIONS = { timeoutMs: 15 * 60 * 1000, maxResponseBytes: 64 * 1024 * 1024 } as const;\n\nexport type ApiFetchOptions = RequestInit & {\n useEnvironmentApiKey?: boolean;\n timeoutMs?: number;\n maxResponseBytes?: number;\n};\n\nexport class ApiTimeoutError extends Error {\n readonly code = \"OPENPOND_API_TIMEOUT\";\n\n constructor(readonly timeoutMs: number, readonly requestUrl: string) {\n super(`API request timed out after ${timeoutMs}ms: ${requestUrl}`);\n this.name = \"ApiTimeoutError\";\n }\n}\n\nexport class ApiResponseTooLargeError extends Error {\n readonly code = \"OPENPOND_API_RESPONSE_TOO_LARGE\";\n\n constructor(readonly maximumBytes: number, readonly requestUrl: string) {\n super(`API response exceeded ${maximumBytes} bytes: ${requestUrl}`);\n this.name = \"ApiResponseTooLargeError\";\n }\n}\n\nexport class OpenPondApiError extends Error {\n readonly code: string;\n\n constructor(\n readonly status: number,\n errorCode: string | null,\n label: string,\n readonly apiMessage: string | null = null,\n ) {\n const detail = apiMessage || errorCode;\n super(`${label} failed: ${status}${detail ? ` ${detail}` : \"\"}`);\n this.name = \"OpenPondApiError\";\n this.code = errorCode || \"OPENPOND_API_ERROR\";\n }\n}\n\nexport async function apiFetch(\n baseUrl: string,\n token: string | null,\n requestPath: string,\n options: ApiFetchOptions = {},\n): Promise<Response> {\n const { timeoutMs = DEFAULT_API_TIMEOUT_MS, maxResponseBytes = DEFAULT_API_RESPONSE_BYTES, useEnvironmentApiKey = true, ...init } = options;\n const requestUrl = `${baseUrl}${requestPath}`;\n const headers = new Headers(init.headers);\n headers.set(\"Content-Type\", \"application/json\");\n const apiKey = useEnvironmentApiKey ? process.env.OPENPOND_API_KEY : undefined;\n const trimmedToken = token?.trim() || \"\";\n const tokenIsApiKey = trimmedToken.startsWith(\"opk_\");\n const effectiveApiKey = apiKey || (tokenIsApiKey ? trimmedToken : null);\n if (effectiveApiKey && !headers.has(\"openpond-api-key\")) headers.set(\"openpond-api-key\", effectiveApiKey);\n if (token) {\n headers.set(\"Authorization\", tokenIsApiKey ? `ApiKey ${trimmedToken}` : `Bearer ${token}`);\n } else if (apiKey && !headers.has(\"Authorization\")) {\n headers.set(\"Authorization\", `ApiKey ${apiKey}`);\n }\n\n const timeoutController = new AbortController();\n const timeoutError = new ApiTimeoutError(timeoutMs, requestUrl);\n const timer = timeoutMs > 0\n ? setTimeout(() => timeoutController.abort(timeoutError), timeoutMs)\n : null;\n timer?.unref?.();\n const signal = composedSignal(init.signal, timeoutController.signal, timeoutMs);\n const cleanup = () => {\n if (timer) clearTimeout(timer);\n };\n\n try {\n const response = await fetch(requestUrl, { ...init, headers, signal });\n return boundedResponse(response, {\n cleanup,\n maximumBytes: maxResponseBytes,\n requestUrl,\n timeoutController,\n timeoutError,\n });\n } catch (error) {\n cleanup();\n if (timeoutController.signal.aborted) throw timeoutError;\n throw error;\n }\n}\n\nexport async function readApiJson<T>(response: Response, label: string): Promise<T> {\n let payload: T & { error?: unknown; message?: unknown };\n try {\n const text = await response.text();\n payload = (text ? JSON.parse(text) : {}) as T & { error?: unknown; message?: unknown };\n } catch (error) {\n if (error instanceof ApiTimeoutError || error instanceof ApiResponseTooLargeError) throw error;\n payload = {} as T & { error?: unknown; message?: unknown };\n }\n if (!response.ok) {\n const errorCode = typeof payload.error === \"string\" ? payload.error : null;\n const apiMessage =\n typeof payload.message === \"string\" ? payload.message : null;\n throw new OpenPondApiError(response.status, errorCode, label, apiMessage);\n }\n return payload as T;\n}\n\nfunction boundedResponse(\n response: Response,\n input: {\n cleanup: () => void;\n maximumBytes: number;\n requestUrl: string;\n timeoutController: AbortController;\n timeoutError: ApiTimeoutError;\n },\n): Response {\n if (!response.body) {\n input.cleanup();\n return response;\n }\n const contentLength = Number(response.headers.get(\"content-length\"));\n if (input.maximumBytes > 0 && Number.isFinite(contentLength) && contentLength > input.maximumBytes) {\n input.cleanup();\n void response.body.cancel();\n throw new ApiResponseTooLargeError(input.maximumBytes, input.requestUrl);\n }\n\n const reader = response.body.getReader();\n let receivedBytes = 0;\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n try {\n const result = await reader.read();\n if (result.done) {\n input.cleanup();\n controller.close();\n return;\n }\n receivedBytes += result.value.byteLength;\n if (input.maximumBytes > 0 && receivedBytes > input.maximumBytes) {\n input.cleanup();\n await reader.cancel();\n controller.error(new ApiResponseTooLargeError(input.maximumBytes, input.requestUrl));\n return;\n }\n controller.enqueue(result.value);\n } catch (error) {\n input.cleanup();\n controller.error(input.timeoutController.signal.aborted ? input.timeoutError : error);\n }\n },\n async cancel(reason) {\n input.cleanup();\n await reader.cancel(reason);\n },\n });\n return new Response(body, {\n headers: response.headers,\n status: response.status,\n statusText: response.statusText,\n });\n}\n\nfunction composedSignal(\n callerSignal: AbortSignal | null | undefined,\n timeoutSignal: AbortSignal,\n timeoutMs: number,\n): AbortSignal | undefined {\n if (timeoutMs <= 0) return callerSignal ?? undefined;\n return callerSignal ? AbortSignal.any([callerSignal, timeoutSignal]) : timeoutSignal;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  apiFetch,
3
3
  readApiJson
4
- } from "./chunk-APHPXQEA.js";
4
+ } from "./chunk-22RMONJA.js";
5
5
 
6
6
  // src/profile-actions.ts
7
7
  var OpenPondProfileActionsClient = class {
@@ -69,4 +69,4 @@ function requiredValue(value, name) {
69
69
  export {
70
70
  OpenPondProfileActionsClient
71
71
  };
72
- //# sourceMappingURL=chunk-QLEJOCQX.js.map
72
+ //# sourceMappingURL=chunk-6T23L3LH.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  apiFetch,
3
3
  readApiJson
4
- } from "./chunk-APHPXQEA.js";
4
+ } from "./chunk-22RMONJA.js";
5
5
 
6
6
  // src/project-actions.ts
7
7
  import { promises as fs } from "node:fs";
@@ -80,4 +80,4 @@ function projectActionPath(projectId, suffix, teamId) {
80
80
  export {
81
81
  OpenPondProjectActionsClient
82
82
  };
83
- //# sourceMappingURL=chunk-27ETSPHQ.js.map
83
+ //# sourceMappingURL=chunk-FJ7IMEG5.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  apiFetch,
3
3
  readApiJson
4
- } from "./chunk-APHPXQEA.js";
4
+ } from "./chunk-22RMONJA.js";
5
5
 
6
6
  // src/workflows.ts
7
7
  import { randomUUID } from "node:crypto";
@@ -77,4 +77,4 @@ function requiredId(value, label) {
77
77
  export {
78
78
  OpenPondWorkflowsClient
79
79
  };
80
- //# sourceMappingURL=chunk-7O5X7DBU.js.map
80
+ //# sourceMappingURL=chunk-JHKNPQD5.js.map
package/dist/index.js CHANGED
@@ -5,18 +5,18 @@ import {
5
5
  import "./chunk-5FBPNWT4.js";
6
6
  import {
7
7
  OpenPondProjectActionsClient
8
- } from "./chunk-27ETSPHQ.js";
8
+ } from "./chunk-FJ7IMEG5.js";
9
9
  import {
10
10
  OpenPondProfileActionsClient
11
- } from "./chunk-QLEJOCQX.js";
11
+ } from "./chunk-6T23L3LH.js";
12
12
  import {
13
13
  OpenPondWorkflowsClient
14
- } from "./chunk-7O5X7DBU.js";
14
+ } from "./chunk-JHKNPQD5.js";
15
15
  import {
16
16
  OpenPondApiError,
17
17
  apiFetch,
18
18
  readApiJson
19
- } from "./chunk-APHPXQEA.js";
19
+ } from "./chunk-22RMONJA.js";
20
20
  import "./chunk-LXXQVGQY.js";
21
21
  import {
22
22
  DEFAULT_REFINER_REVIEW_PROFILE,
@@ -630,14 +630,17 @@ var OpenPondSandboxInstanceClient = class {
630
630
  apiKey;
631
631
  apiRootUrl;
632
632
  sandboxApiUrl;
633
+ customEndpoint;
633
634
  constructor(options) {
634
635
  this.apiKey = options.apiKey;
636
+ this.customEndpoint = options.customEndpoint ?? false;
635
637
  this.sandboxApiUrl = normalizeSandboxApiUrl(options.sandboxApiUrl ?? options.baseUrl ?? DEFAULT_OPENPOND_API_BASE_URL);
636
638
  this.apiRootUrl = apiRootUrlFromSandboxApiUrl(this.sandboxApiUrl);
637
639
  }
638
640
  create(input, options = {}) {
639
641
  return this.request("", {
640
642
  method: "POST",
643
+ ...this.customEndpoint ? { timeoutMs: 18e4 } : {},
641
644
  headers: asyncRequestHeaders(options),
642
645
  body: JSON.stringify(input)
643
646
  }).then((payload) => payload.sandbox);
@@ -940,11 +943,11 @@ var OpenPondSandboxInstanceClient = class {
940
943
  });
941
944
  }
942
945
  async request(path, init = {}) {
943
- const response = await apiFetch(this.sandboxApiUrl, this.apiKey, path, init);
946
+ const response = await apiFetch(this.sandboxApiUrl, this.apiKey, path, this.customEndpoint ? { ...init, useEnvironmentApiKey: false, redirect: "error" } : init);
944
947
  return readApiJson(response, "Sandbox request");
945
948
  }
946
949
  async requestApiRoot(path, init = {}) {
947
- const response = await apiFetch(this.apiRootUrl, this.apiKey, path, init);
950
+ const response = await apiFetch(this.apiRootUrl, this.apiKey, path, this.customEndpoint ? { ...init, useEnvironmentApiKey: false, redirect: "error" } : init);
948
951
  return readApiJson(response, "OpenPond API request");
949
952
  }
950
953
  };
@@ -1834,6 +1837,77 @@ async function* streamHostedChatTurn(options) {
1834
1837
  }
1835
1838
  }
1836
1839
 
1840
+ // src/work-sandbox.ts
1841
+ function workSandbox(client, custom) {
1842
+ return {
1843
+ supportsResume: !custom,
1844
+ get: (...args) => client.get(...args),
1845
+ start: custom ? async () => {
1846
+ throw new Error("This runtime does not support resuming a stopped guest; start fresh Work with saved outputs");
1847
+ } : (...args) => client.start(...args),
1848
+ exec: (id, input) => client.exec(id, custom ? { ...input, timeoutSeconds: Math.min(input.timeoutSeconds ?? 30, 60) } : input),
1849
+ mkdir: (...args) => client.mkdir(...args),
1850
+ uploadFile: (...args) => client.uploadFile(...args),
1851
+ uploadFileBase64: (...args) => client.uploadFileBase64(...args),
1852
+ listFiles: (...args) => client.listFiles(...args),
1853
+ downloadFileResponse: (...args) => client.downloadFileResponse(...args),
1854
+ stop: (...args) => client.stop(...args),
1855
+ delete: (...args) => client.delete(...args),
1856
+ createWork: ({ requestId, repo, budgetUsd, metadata }) => {
1857
+ if (custom) {
1858
+ if (repo?.trim()) throw new Error("This runtime does not support repository provisioning; provide Work inputs instead");
1859
+ if (budgetUsd !== void 0) throw new Error("Hosted spending budgets are not supported by this runtime");
1860
+ const request = { resources: custom.resources ?? { cpu: 1, memoryGb: 2, diskGb: 10 }, ...requestId ? { requestId } : {} };
1861
+ return client.create(request);
1862
+ }
1863
+ const budget = budgetUsd?.trim() || "1.00";
1864
+ return client.create({
1865
+ ...repo?.trim() ? { repo: repo.trim() } : {},
1866
+ runtimeProfileId: "openpond-work-v1",
1867
+ resources: { cpu: 2, memoryGb: 4, diskGb: 16 },
1868
+ budget: { maxUsd: budget },
1869
+ quotas: { maxSpendUsd: budget, maxDurationSeconds: 3600, idleTimeoutSeconds: 900, maxCommands: 200, maxOpenPorts: 4 },
1870
+ metadata: { source: "openpond-sdk-work", ...metadata, ...requestId ? { workRequestId: requestId } : {} }
1871
+ }, { async: true });
1872
+ }
1873
+ };
1874
+ }
1875
+ function configuredEndpoint(value, name) {
1876
+ const endpoint = new URL(value);
1877
+ if (!["http:", "https:"].includes(endpoint.protocol) || endpoint.username || endpoint.password || endpoint.search || endpoint.hash)
1878
+ throw new Error(`${name} must be an HTTP(S) endpoint without embedded credentials, query or fragment`);
1879
+ return endpoint.toString().replace(/\/$/, "");
1880
+ }
1881
+ function configuredKey(value, name) {
1882
+ if (!value?.trim()) throw new Error(`${name} is required`);
1883
+ return value.trim();
1884
+ }
1885
+
1886
+ // src/work-model.ts
1887
+ import { z } from "zod";
1888
+ var completionSchema = z.object({
1889
+ choices: z.array(z.object({ message: z.object({
1890
+ content: z.string().nullable().optional(),
1891
+ tool_calls: z.array(z.object({
1892
+ id: z.string().min(1),
1893
+ type: z.literal("function"),
1894
+ function: z.object({ name: z.string().min(1), arguments: z.string() })
1895
+ })).optional()
1896
+ }) })).min(1)
1897
+ });
1898
+ async function workModelTurn(options, custom) {
1899
+ if (!custom) return sendHostedChatTurn(options);
1900
+ const response = await fetch(`${options.apiBaseUrl}/chat/completions`, {
1901
+ method: "POST",
1902
+ headers: { authorization: `Bearer ${options.token}`, "content-type": "application/json" },
1903
+ body: JSON.stringify({ model: options.model, messages: options.messages, tools: options.tools, tool_choice: "auto", stream: false }),
1904
+ signal: options.signal,
1905
+ redirect: "error"
1906
+ });
1907
+ if (!response.ok) throw new Error(`Configured model request failed (${response.status})`);
1908
+ return completionSchema.parse(await response.json());
1909
+ }
1910
+
1837
1911
  // src/work.ts
1838
1912
  var DEFAULT_MODEL = "openpond-chat";
1839
1913
  var DEFAULT_MAX_STEPS = 24;
@@ -1850,11 +1924,15 @@ var OpenPondWorkClient = class {
1850
1924
  #apiBaseUrl;
1851
1925
  #chatApiBaseUrl;
1852
1926
  #sandboxes;
1927
+ #defaultModel;
1928
+ #customModel;
1853
1929
  constructor(input) {
1854
1930
  this.#apiKey = input.apiKey;
1855
1931
  this.#apiBaseUrl = input.apiBaseUrl;
1856
1932
  this.#chatApiBaseUrl = input.chatApiBaseUrl;
1857
- this.#sandboxes = input.sandboxes;
1933
+ this.#sandboxes = workSandbox(input.sandboxes, input.sandboxConfig);
1934
+ this.#defaultModel = input.defaultModel ?? DEFAULT_MODEL;
1935
+ this.#customModel = input.customModel ?? false;
1858
1936
  }
1859
1937
  async run(input) {
1860
1938
  const prompt = input.prompt.trim();
@@ -1889,10 +1967,10 @@ var OpenPondWorkClient = class {
1889
1967
  for (let step = 1; step <= maxSteps; step += 1) {
1890
1968
  input.signal?.throwIfAborted();
1891
1969
  await emit({ type: "status", message: `Thinking \xB7 step ${step}` });
1892
- const completion = await sendHostedChatTurn({
1970
+ const completion = await workModelTurn({
1893
1971
  apiBaseUrl: this.#chatApiBaseUrl,
1894
1972
  token: this.#apiKey,
1895
- model: input.model?.trim() || DEFAULT_MODEL,
1973
+ model: input.model?.trim() || this.#defaultModel,
1896
1974
  messages,
1897
1975
  tools: WORK_TOOLS,
1898
1976
  toolChoice: "auto",
@@ -1903,7 +1981,7 @@ var OpenPondWorkClient = class {
1903
1981
  apiBaseUrl: this.#apiBaseUrl,
1904
1982
  ...input.metadata
1905
1983
  }
1906
- });
1984
+ }, this.#customModel);
1907
1985
  const choice = completion.choices?.[0];
1908
1986
  const assistantText = choice?.message?.content?.trim() ?? "";
1909
1987
  const toolCalls = choice?.message?.tool_calls ?? [];
@@ -1932,6 +2010,7 @@ var OpenPondWorkClient = class {
1932
2010
  return result;
1933
2011
  }
1934
2012
  for (const [index, toolCall] of toolCalls.entries()) {
2013
+ input.signal?.throwIfAborted();
1935
2014
  const result = await this.#executeTool(
1936
2015
  sandbox.id,
1937
2016
  toolCall,
@@ -1958,9 +2037,14 @@ var OpenPondWorkClient = class {
1958
2037
  }
1959
2038
  }
1960
2039
  async deleteSandbox(sandboxId) {
1961
- const sandbox = await this.#sandboxes.get(sandboxId);
1962
- if (sandbox.state === "deleted") return;
1963
- await this.#sandboxes.delete(sandboxId);
2040
+ try {
2041
+ const sandbox = await this.#sandboxes.get(sandboxId);
2042
+ if (sandbox.state === "deleted") return;
2043
+ await this.#sandboxes.delete(sandboxId);
2044
+ } catch (error) {
2045
+ if (error instanceof OpenPondApiError && error.status === 404) return;
2046
+ throw error;
2047
+ }
1964
2048
  }
1965
2049
  async #stageInputs(sandboxId, inputs) {
1966
2050
  if (inputs.length === 0) return;
@@ -2028,7 +2112,7 @@ var OpenPondWorkClient = class {
2028
2112
  error: message
2029
2113
  });
2030
2114
  if ((input.cleanup ?? "keep") !== "keep") {
2031
- await this.#cleanupSandbox(sandboxId, "stop", lifecycle, emit);
2115
+ await this.#preserveFailedOutputs(sandboxId, lifecycle, emit);
2032
2116
  }
2033
2117
  throw error;
2034
2118
  }
@@ -2037,7 +2121,7 @@ var OpenPondWorkClient = class {
2037
2121
  } else if ((input.cleanup ?? "keep") === "delete" && !input.discardOutputs) {
2038
2122
  lifecycle.persistence.status = "failed";
2039
2123
  lifecycle.persistence.error = "Deleting a Work sandbox with outputs requires persistOutput or discardOutputs: true";
2040
- await this.#cleanupSandbox(sandboxId, "stop", lifecycle, emit);
2124
+ await this.#preserveFailedOutputs(sandboxId, lifecycle, emit);
2041
2125
  throw new Error(lifecycle.persistence.error);
2042
2126
  } else {
2043
2127
  lifecycle.persistence.status = "not_requested";
@@ -2049,6 +2133,16 @@ var OpenPondWorkClient = class {
2049
2133
  emit
2050
2134
  );
2051
2135
  }
2136
+ async #preserveFailedOutputs(sandboxId, lifecycle, emit) {
2137
+ if (this.#sandboxes.supportsResume) {
2138
+ await this.#cleanupSandbox(sandboxId, "stop", lifecycle, emit);
2139
+ return;
2140
+ }
2141
+ lifecycle.cleanup.status = "pending";
2142
+ lifecycle.cleanup.finalSandboxState = "running";
2143
+ lifecycle.cleanup.error = "Output persistence must be retried before guest deletion";
2144
+ await emit({ type: "cleanup", policy: lifecycle.cleanupPolicy, status: "pending", sandboxState: "running", error: lifecycle.cleanup.error });
2145
+ }
2052
2146
  async #cleanupSandbox(sandboxId, policy, lifecycle, emit) {
2053
2147
  if (policy === "keep") return;
2054
2148
  lifecycle.cleanup.status = "running";
@@ -2109,24 +2203,7 @@ var OpenPondWorkClient = class {
2109
2203
  const ready = await this.#waitUntilRunning(existing.id, input.signal);
2110
2204
  return ready;
2111
2205
  }
2112
- const budgetUsd = input.budgetUsd?.trim() || "1.00";
2113
- const created = await this.#sandboxes.create(
2114
- {
2115
- ...input.repo?.trim() ? { repo: input.repo.trim() } : {},
2116
- runtimeProfileId: "openpond-work-v1",
2117
- resources: { cpu: 2, memoryGb: 4, diskGb: 16 },
2118
- budget: { maxUsd: budgetUsd },
2119
- quotas: {
2120
- maxSpendUsd: budgetUsd,
2121
- maxDurationSeconds: 3600,
2122
- idleTimeoutSeconds: 900,
2123
- maxCommands: 200,
2124
- maxOpenPorts: 4
2125
- },
2126
- metadata: { source: "openpond-sdk-work", ...input.metadata }
2127
- },
2128
- { async: true }
2129
- );
2206
+ const created = await this.#sandboxes.createWork(input);
2130
2207
  return this.#waitUntilRunning(created.id, input.signal);
2131
2208
  }
2132
2209
  async #waitUntilRunning(sandboxId, signal) {
@@ -2381,20 +2458,35 @@ var OpenPondClient = class {
2381
2458
  profileActions;
2382
2459
  learning;
2383
2460
  constructor(options) {
2384
- const apiKey = options.apiKey.trim();
2385
- if (!apiKey) throw new Error("OpenPond API key is required");
2461
+ const apiKey = options.apiKey?.trim() ?? "";
2462
+ if (!apiKey && (!options.sandbox || !options.model))
2463
+ throw new Error("OpenPond API key is required when using a default sandbox or model");
2464
+ const sandbox = options.sandbox ? {
2465
+ ...options.sandbox,
2466
+ endpoint: configuredEndpoint(options.sandbox.endpoint, "Sandbox endpoint"),
2467
+ apiKey: configuredKey(options.sandbox.apiKey, "Sandbox API key")
2468
+ } : void 0;
2469
+ const model = options.model ? {
2470
+ endpoint: configuredEndpoint(options.model.endpoint, "Model endpoint"),
2471
+ apiKey: configuredKey(options.model.apiKey, "Model API key"),
2472
+ model: configuredKey(options.model.model, "Model ID")
2473
+ } : void 0;
2386
2474
  const apiBaseUrl = options.baseUrl?.trim() || "https://api.openpond.ai";
2387
2475
  this.learning = (scope) => new OpenPondLearningClient({ apiKey, baseUrl: apiBaseUrl, scope });
2388
2476
  this.sandboxes = createOpenPondSandboxClient({
2389
- apiKey,
2477
+ apiKey: sandbox?.apiKey ?? apiKey,
2390
2478
  baseUrl: apiBaseUrl,
2391
- sandboxApiUrl: options.sandboxApiUrl
2479
+ sandboxApiUrl: sandbox?.endpoint ?? options.sandboxApiUrl,
2480
+ customEndpoint: Boolean(sandbox)
2392
2481
  });
2393
2482
  this.work = new OpenPondWorkClient({
2394
- apiKey,
2483
+ apiKey: model?.apiKey ?? apiKey,
2395
2484
  apiBaseUrl,
2396
- chatApiBaseUrl: options.chatApiUrl?.trim() || resolveOpChatApiBaseUrl({ apiBaseUrl, env: {} }),
2397
- sandboxes: this.sandboxes
2485
+ chatApiBaseUrl: model?.endpoint || options.chatApiUrl?.trim() || resolveOpChatApiBaseUrl({ apiBaseUrl, env: {} }),
2486
+ sandboxes: this.sandboxes,
2487
+ sandboxConfig: sandbox,
2488
+ defaultModel: model?.model,
2489
+ customModel: Boolean(model)
2398
2490
  });
2399
2491
  this.workflows = new OpenPondWorkflowsClient({ apiKey, apiBaseUrl });
2400
2492
  this.actions = new OpenPondProjectActionsClient({ apiKey, apiBaseUrl });