openpond-sdk 0.12.23 → 0.12.25

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 (123) hide show
  1. package/PRODUCT_INTEGRATION.md +134 -0
  2. package/README.md +12 -1
  3. package/TASK_INGESTION.md +88 -86
  4. package/TRAINING_PROTOCOL.md +6 -2
  5. package/TRAINING_WORKFLOWS.md +16 -0
  6. package/dist/advanced-refiner-evaluations.js +3 -3
  7. package/dist/ai-sdk.js +224 -0
  8. package/dist/ai-sdk.js.map +1 -0
  9. package/dist/{chunk-NZRGRUYG.js → chunk-2OVNKPBZ.js} +17 -6
  10. package/dist/chunk-2OVNKPBZ.js.map +1 -0
  11. package/dist/{chunk-QIGMNB6M.js → chunk-3G3HUA32.js} +3 -3
  12. package/dist/{chunk-7G47YXQT.js → chunk-3MWFN7BA.js} +2 -2
  13. package/dist/{chunk-3BYFS4RO.js → chunk-47THZRUM.js} +5 -5
  14. package/dist/chunk-BRP3EBCR.js +245 -0
  15. package/dist/chunk-BRP3EBCR.js.map +1 -0
  16. package/dist/{chunk-BNBPKCIR.js → chunk-DDCR4KB5.js} +4 -4
  17. package/dist/{chunk-ZQ6QTYRE.js → chunk-GV6DEDBU.js} +10 -10
  18. package/dist/{chunk-ZQ6QTYRE.js.map → chunk-GV6DEDBU.js.map} +1 -1
  19. package/dist/{chunk-QCRNQMN4.js → chunk-IXFUYPGV.js} +2 -2
  20. package/dist/{chunk-Q4RGCEOI.js → chunk-JOMZJXGC.js} +3 -3
  21. package/dist/{chunk-OBI2MQYU.js → chunk-QRYEXPG7.js} +7 -7
  22. package/dist/{chunk-6EZAGTZG.js → chunk-RN5MK7NH.js} +4 -4
  23. package/dist/{chunk-GCUWJUUM.js → chunk-RTLJV4OS.js} +2 -2
  24. package/dist/{chunk-GBDBKGXW.js → chunk-T67LNKAK.js} +3 -3
  25. package/dist/{chunk-SP7DQEQM.js → chunk-XBFOFTK3.js} +5 -5
  26. package/dist/{chunk-AG6UJ3DI.js → chunk-YS7DF76T.js} +2 -2
  27. package/dist/{chunk-LEVXD73U.js → chunk-Z5APNTZE.js} +197 -190
  28. package/dist/chunk-Z5APNTZE.js.map +1 -0
  29. package/dist/connected-evidence.js +8 -8
  30. package/dist/dataset-workspaces.js +146 -85
  31. package/dist/dataset-workspaces.js.map +1 -1
  32. package/dist/experiment-evaluation-schedules.js +11 -11
  33. package/dist/experiments.js +15 -15
  34. package/dist/index.js +71 -44
  35. package/dist/index.js.map +1 -1
  36. package/dist/model-starter-catalog.js +2 -2
  37. package/dist/model-starters.js +2 -2
  38. package/dist/taskset-drafts.js +7 -7
  39. package/dist/taskset-packages.js +13 -13
  40. package/dist/training-bundle.js +12 -12
  41. package/dist/training.js +1936 -1809
  42. package/dist/training.js.map +1 -1
  43. package/dist/types/packages/sdk/src/ai-sdk.d.ts +6 -0
  44. package/dist/types/packages/sdk/src/ai-sdk.d.ts.map +1 -0
  45. package/dist/types/packages/sdk/src/candidate-evaluation-client.d.ts +21 -21
  46. package/dist/types/packages/sdk/src/dataset-task-ingestion.d.ts +8 -1
  47. package/dist/types/packages/sdk/src/dataset-task-ingestion.d.ts.map +1 -1
  48. package/dist/types/packages/sdk/src/dataset-workspace-client.d.ts +140 -140
  49. package/dist/types/packages/sdk/src/dataset-workspace-client.d.ts.map +1 -1
  50. package/dist/types/packages/sdk/src/dataset-workspace-error.d.ts +28 -0
  51. package/dist/types/packages/sdk/src/dataset-workspace-error.d.ts.map +1 -0
  52. package/dist/types/packages/sdk/src/dataset-workspaces.d.ts +1 -0
  53. package/dist/types/packages/sdk/src/dataset-workspaces.d.ts.map +1 -1
  54. package/dist/types/packages/sdk/src/experiment-client.d.ts +636 -636
  55. package/dist/types/packages/sdk/src/experiment-draft-storage.d.ts +124 -34
  56. package/dist/types/packages/sdk/src/experiment-draft-storage.d.ts.map +1 -1
  57. package/dist/types/packages/sdk/src/experiment-evaluation-schedules.d.ts +28 -28
  58. package/dist/types/packages/sdk/src/experiment-improvement-client.d.ts +1622 -1622
  59. package/dist/types/packages/sdk/src/experiment-run-details.d.ts +7 -7
  60. package/dist/types/packages/sdk/src/index.d.ts +13 -4
  61. package/dist/types/packages/sdk/src/index.d.ts.map +1 -1
  62. package/dist/types/packages/sdk/src/managed-decision-executions.d.ts +347 -347
  63. package/dist/types/packages/sdk/src/managed-neuron-training-executions.d.ts +242 -21
  64. package/dist/types/packages/sdk/src/managed-neuron-training-executions.d.ts.map +1 -1
  65. package/dist/types/packages/sdk/src/managed-training-preparation.d.ts +25 -25
  66. package/dist/types/packages/sdk/src/model-taskset-runs.d.ts +28 -28
  67. package/dist/types/packages/sdk/src/neuron-training-artifact.d.ts +42 -3
  68. package/dist/types/packages/sdk/src/neuron-training-artifact.d.ts.map +1 -1
  69. package/dist/types/packages/sdk/src/neuron-training-experiment-draft.d.ts +16 -1
  70. package/dist/types/packages/sdk/src/neuron-training-experiment-draft.d.ts.map +1 -1
  71. package/dist/types/packages/sdk/src/neuron-training-model-serving.d.ts +168 -16
  72. package/dist/types/packages/sdk/src/neuron-training-model-serving.d.ts.map +1 -1
  73. package/dist/types/packages/sdk/src/neuron-training-result.d.ts +42 -3
  74. package/dist/types/packages/sdk/src/neuron-training-result.d.ts.map +1 -1
  75. package/dist/types/packages/sdk/src/neuron-training.d.ts +23 -2
  76. package/dist/types/packages/sdk/src/neuron-training.d.ts.map +1 -1
  77. package/dist/types/packages/sdk/src/post-training.d.ts +7 -7
  78. package/dist/types/packages/sdk/src/product-model-response.d.ts +4 -0
  79. package/dist/types/packages/sdk/src/product-model-response.d.ts.map +1 -0
  80. package/dist/types/packages/sdk/src/product-models.d.ts +76 -0
  81. package/dist/types/packages/sdk/src/product-models.d.ts.map +1 -0
  82. package/dist/types/packages/sdk/src/steering-model-serving.d.ts +42 -4
  83. package/dist/types/packages/sdk/src/steering-model-serving.d.ts.map +1 -1
  84. package/dist/types/packages/sdk/src/steering-profile-publication.d.ts +84 -8
  85. package/dist/types/packages/sdk/src/steering-profile-publication.d.ts.map +1 -1
  86. package/dist/types/packages/sdk/src/taskset-draft-source.d.ts +20 -20
  87. package/dist/types/packages/sdk/src/taskset-package-projection.d.ts +20 -20
  88. package/dist/types/packages/sdk/src/training-evidence.d.ts +54 -0
  89. package/dist/types/packages/sdk/src/training-evidence.d.ts.map +1 -0
  90. package/dist/types/packages/sdk/src/training-experiment-setup.d.ts +42 -2
  91. package/dist/types/packages/sdk/src/training-experiment-setup.d.ts.map +1 -1
  92. package/dist/types/packages/sdk/src/training-harness-sources.d.ts +2 -2
  93. package/dist/types/packages/sdk/src/training-learning-batch.d.ts +40 -40
  94. package/dist/types/packages/sdk/src/training-preparations.d.ts +1770 -485
  95. package/dist/types/packages/sdk/src/training-preparations.d.ts.map +1 -1
  96. package/dist/types/packages/sdk/src/training-qualification-preparation.d.ts +25 -25
  97. package/dist/types/packages/sdk/src/training-usage-funding.d.ts +23 -0
  98. package/dist/types/packages/sdk/src/training-usage-funding.d.ts.map +1 -1
  99. package/dist/types/packages/sdk/src/training.d.ts +5189 -4445
  100. package/dist/types/packages/sdk/src/training.d.ts.map +1 -1
  101. package/dist/types/packages/sdk/src/types.d.ts +14 -1
  102. package/dist/types/packages/sdk/src/types.d.ts.map +1 -1
  103. package/dist/types/packages/sdk/src/weight-experiment-authoring.d.ts +5 -2
  104. package/dist/types/packages/sdk/src/weight-experiment-authoring.d.ts.map +1 -1
  105. package/dist/types/packages/sdk/src/weight-model-versions.d.ts +168 -16
  106. package/dist/types/packages/sdk/src/weight-model-versions.d.ts.map +1 -1
  107. package/fixtures/personalized-inference-request.schema.json +316 -0
  108. package/fixtures/python-foundation/schemas.json +8750 -8351
  109. package/package.json +18 -3
  110. package/dist/chunk-LEVXD73U.js.map +0 -1
  111. package/dist/chunk-NZRGRUYG.js.map +0 -1
  112. /package/dist/{chunk-QIGMNB6M.js.map → chunk-3G3HUA32.js.map} +0 -0
  113. /package/dist/{chunk-7G47YXQT.js.map → chunk-3MWFN7BA.js.map} +0 -0
  114. /package/dist/{chunk-3BYFS4RO.js.map → chunk-47THZRUM.js.map} +0 -0
  115. /package/dist/{chunk-BNBPKCIR.js.map → chunk-DDCR4KB5.js.map} +0 -0
  116. /package/dist/{chunk-QCRNQMN4.js.map → chunk-IXFUYPGV.js.map} +0 -0
  117. /package/dist/{chunk-Q4RGCEOI.js.map → chunk-JOMZJXGC.js.map} +0 -0
  118. /package/dist/{chunk-OBI2MQYU.js.map → chunk-QRYEXPG7.js.map} +0 -0
  119. /package/dist/{chunk-6EZAGTZG.js.map → chunk-RN5MK7NH.js.map} +0 -0
  120. /package/dist/{chunk-GCUWJUUM.js.map → chunk-RTLJV4OS.js.map} +0 -0
  121. /package/dist/{chunk-GBDBKGXW.js.map → chunk-T67LNKAK.js.map} +0 -0
  122. /package/dist/{chunk-SP7DQEQM.js.map → chunk-XBFOFTK3.js.map} +0 -0
  123. /package/dist/{chunk-AG6UJ3DI.js.map → chunk-YS7DF76T.js.map} +0 -0
@@ -0,0 +1,134 @@
1
+ # Use OpenPond in your product
2
+
3
+ The APIs below target the **0.12.25 source candidate**, which is not published
4
+ yet. Registry version 0.12.24 does not include configured Dataset/named-model
5
+ setup. Use the built candidate package until the coordinated release.
6
+
7
+
8
+ Configure one application-owned server client. Bind aliases to exact callable
9
+ Model IDs from your serving destination:
10
+
11
+ ```ts
12
+ import { createOpenPondClient } from "openpond-sdk";
13
+
14
+ export const openpond = createOpenPondClient({
15
+ apiKey: process.env.OPENPOND_API_KEY!,
16
+ teamId: process.env.OPENPOND_TEAM_ID!,
17
+ datasetId: process.env.OPENPOND_DATASET_ID!,
18
+ serving: {
19
+ baseUrl: process.env.OPENPOND_SERVING_BASE_URL!,
20
+ apiKey: process.env.OPENPOND_SERVING_API_KEY!,
21
+ },
22
+ models: { support: process.env.OPENPOND_MODEL_ID! },
23
+ });
24
+
25
+ const answer = await openpond.models.support.generate({ prompt: "How do I rotate a token?" });
26
+ console.log(answer.text, answer.requestId, answer.usage);
27
+ ```
28
+
29
+ `serving.baseUrl` is the personalized OpenAI-compatible Chat Completions base URL,
30
+ including its `/v1` path. It is configured explicitly: no catalogue default or
31
+ substitute Model is selected. Omit `serving.apiKey` only when this destination
32
+ accepts the main API key. Naming a Model never activates serving, approves a
33
+ candidate, starts training, or automatically captures inference as a task.
34
+ Decision scorers use their separate typed scoring APIs.
35
+
36
+ `generate` accepts either `prompt` or text `messages`, plus optional `temperature`,
37
+ `maxTokens` and `signal`. Results include `text`, `requestId`, requested `modelId`,
38
+ server-reported `resolvedModelId`, `finishReason`, nullable `usage`, and nullable
39
+ `systemFingerprint`, plus retained server `evidence` when supplied. Evidence
40
+ presence is not client-side verification. Core handles are text APIs; tools need
41
+ the supported integration and explicit capabilities. The current personalized API
42
+ rejects multimodal and structured-output modes. Aliases are typed TypeScript
43
+ properties; multiple clients remain independent.
44
+
45
+ ```ts
46
+ for await (const event of openpond.models.support.stream({ prompt: "Explain token rotation" })) {
47
+ if (event.type === "text") process.stdout.write(event.text);
48
+ else console.log(event.result.usage);
49
+ }
50
+ ```
51
+
52
+ Breaking the iteration cancels the response. An interrupted stream raises
53
+ `ProductModelError` and never emits a successful `finish`. It may already have
54
+ incurred usage; inference is never automatically replayed. `serving.timeoutMs`
55
+ defaults to 60 seconds and accepts at most 300 seconds. Supply a caller abort
56
+ signal to cancel an individual request. Text requests are bounded to 1 MiB and
57
+ results to 8 MiB; individual stream frames to 1 MiB.
58
+
59
+ Await task delivery separately with `openpond.addTask(...)`. If inference succeeds
60
+ and optional task delivery fails, preserve both outcomes rather than discarding
61
+ the delivery error or pretending the answer failed. See [task ingestion](TASK_INGESTION.md).
62
+
63
+ ## Python sync and async
64
+
65
+ ```python
66
+ from ponder_sdk import AsyncClient
67
+
68
+ async with AsyncClient(
69
+ base_url=api_origin, api_key=api_key, team_id=team_id,
70
+ dataset_id=dataset_id,
71
+ serving_base_url=serving_base_url, serving_api_key=serving_api_key,
72
+ model_aliases={"support": model_id},
73
+ ) as openpond:
74
+ answer = await openpond.models.support.generate(prompt="How do I rotate a token?")
75
+ print(answer.text, answer.request_id, answer.usage)
76
+ await openpond.add_task({"id": event_id, "name": "Token rotation", "input": {"question": question}})
77
+ async for event in openpond.models.support.stream(prompt="Explain the steps"):
78
+ if event.type == "text":
79
+ print(event.text, end="")
80
+ ```
81
+
82
+ `Client` has the same configuration and synchronous calls/iteration. Named aliases
83
+ augment existing Model administration methods; choose aliases that don't collide
84
+ with those methods. Bracket access (`openpond.models["support"]`) is also available.
85
+ Inference shares the existing client's HTTP pool and timeout. Close partially
86
+ consumed iterators (or use `contextlib.closing`/`aclosing`) to release streams
87
+ promptly. Native async cancellation propagates. No SDK queues or flush calls exist.
88
+
89
+ ## Optional Vercel AI SDK 7 integration
90
+
91
+ Install `ai@^7.0.165` and `@ai-sdk/openai-compatible@^3.0.87` alongside OpenPond.
92
+ Only the `openpond-sdk/ai-sdk` entry point imports the optional provider; task
93
+ submission has no Vercel dependency. This adapter targets LanguageModelV4 and is
94
+ not an upgrade of an application's existing AI SDK 4/5/6 installation.
95
+
96
+ ```ts
97
+ import { generateText, streamText } from "ai";
98
+ import { toAIModel } from "openpond-sdk/ai-sdk";
99
+ import { openpond } from "./openpond.server.js";
100
+
101
+ const model = toAIModel(openpond.models.support);
102
+ const answer = await generateText({ model, prompt: "Explain token rotation", maxRetries: 0 });
103
+
104
+ // Inside an authenticated product backend route:
105
+ export async function POST(request: Request) {
106
+ // Authenticate the product user and validate/limit this payload first.
107
+ const { prompt } = await request.json();
108
+ const result = streamText({ model, prompt, abortSignal: request.signal, maxRetries: 0 });
109
+ return result.toUIMessageStreamResponse();
110
+ }
111
+ ```
112
+
113
+ Use the AI SDK 7 React `useChat`/transport recipe against your own authenticated
114
+ backend. Never place the OpenPond client or secrets in browser code. AI SDK's UI
115
+ stream response is different from raw OpenAI SSE; use
116
+ `toUIMessageStreamResponse()` rather than forwarding raw SSE into `useChat`.
117
+
118
+ The official compatible provider handles content/usage conversion, tool deltas and
119
+ stream cancellation. OpenPond gates features against the selected binding:
120
+
121
+ ```ts
122
+ models: {
123
+ support: {
124
+ id: process.env.OPENPOND_MODEL_ID!,
125
+ capabilities: { streaming: true, tools: true },
126
+ },
127
+ }
128
+ ```
129
+
130
+ Only enable tools after checking that exact deployment.
131
+ Defaults are streaming enabled and tools disabled. Structured-output and multimodal
132
+ inputs are rejected. Failed provider calls are surfaced as non-retryable SDK
133
+ errors to prevent implicit replay of billed requests. Explicit `maxRetries: 0`
134
+ also makes that intent visible in application code.
package/README.md CHANGED
@@ -392,4 +392,15 @@ not implicitly share a spending cap.
392
392
 
393
393
  ## Add production tasks
394
394
 
395
- Use the flat `client.addTask(datasetId, task)` operation, or bind one destination with `createTaskAdder` and call `addTask(task)`. See [task ingestion](TASK_INGESTION.md) for TypeScript/Python examples, receipt semantics and duplicate-safe retries. Task uploads do not enable training.
395
+ The configured API targets unpublished source candidate **0.12.25**; registry 0.12.24 does not provide it. Configure `datasetId` once and await `client.addTask({ id: sourceEventId, name, input })`. Use `client.dataset(otherDatasetId).addTask(task)` for another destination. See [task ingestion](TASK_INGESTION.md) for TypeScript/Python examples, receipt semantics and duplicate-safe retries. Task uploads do not enable training.
396
+
397
+ Dataset workspace HTTP failures throw `DatasetWorkspaceRequestError` from
398
+ `openpond-sdk/dataset-workspaces`, with `status`, an allowlisted `code`,
399
+ `retryable`, and `outcomeUnknown`. Messages contain fixed recovery guidance;
400
+ raw server diagnostics are discarded. `retryable` does not trigger an automatic
401
+ retry. When `outcomeUnknown` is true, retain the original operation ID and request
402
+ bytes, recover its receipt with `operationResult`, or retry the identical request.
403
+
404
+ ## Add OpenPond to a product
405
+
406
+ Configure named personalized Model handles with `models` and an explicit `serving` destination, then call `client.models.support.generate({ prompt })` or iterate `stream({ prompt })`. See [product integration](PRODUCT_INTEGRATION.md) for TypeScript, Python, React/backend and optional Vercel AI SDK 7 examples. Inference never activates serving or automatically captures tasks.
package/TASK_INGESTION.md CHANGED
@@ -1,135 +1,137 @@
1
1
  # Add tasks to a Dataset
2
2
 
3
- Task submission uses an authenticated OpenPond API, separate from OpenTelemetry.
4
- A successful receipt means a structured task was durably saved and is available
5
- through the ordinary Dataset Tasks reader. It does not start training, publish
6
- a release, create a Model or enable a collection schedule.
3
+ The APIs below target the **0.12.25 source candidate**, which is not published
4
+ yet. Registry version 0.12.24 does not include configured Dataset/named-model
5
+ setup. Use the built candidate package until the coordinated release.
7
6
 
8
- Create or choose an editable Dataset in Console and copy its ID. Configure the
9
- server-side SDK with the workspace that owns it:
7
+
8
+ Configure a client once in your product's server module. Submit one structured
9
+ source event with `await addTask(task)`:
10
10
 
11
11
  ```ts
12
12
  import { createOpenPondClient } from "openpond-sdk";
13
13
 
14
- const client = createOpenPondClient({
14
+ export const openpond = createOpenPondClient({
15
15
  apiKey: process.env.OPENPOND_API_KEY!,
16
16
  teamId: process.env.OPENPOND_TEAM_ID!,
17
+ datasetId: process.env.OPENPOND_DATASET_ID!,
17
18
  });
18
19
 
19
- const receipt = await client.addTask(process.env.OPENPOND_DATASET_ID!, {
20
+ const receipt = await openpond.addTask({
21
+ id: sourceEventId,
20
22
  name: "Handle an expired API token",
21
23
  input: { request: "Fetch the report", observedError: "Token expired" },
22
- source: { kind: "production_agent", eventId: "run-123" },
23
- }, { idempotencyKey: "run-123" });
24
-
24
+ source: { kind: "production_agent", eventId: sourceEventId },
25
+ });
25
26
  console.log(receipt.taskId, receipt.receivedAt);
26
27
  ```
27
28
 
28
- Use a configured `baseUrl` when targeting a different OpenPond API origin.
29
- Never expose the API key in browser code or logged task content.
30
-
31
- ## Bind the destination once
29
+ Use `baseUrl` for a different OpenPond API origin. Each configured client keeps
30
+ its own credentials and destination; no mutable global account is used. For an
31
+ explicit alternate Dataset:
32
32
 
33
33
  ```ts
34
- import { createTaskAdder } from "openpond-sdk";
35
-
36
- const addTask = createTaskAdder({
37
- apiKey: process.env.OPENPOND_API_KEY!,
38
- teamId: process.env.OPENPOND_TEAM_ID!,
39
- datasetId: process.env.OPENPOND_DATASET_ID!,
40
- baseUrl: "https://api.openpond.ai",
41
- });
42
-
43
- await addTask({
34
+ await openpond.dataset(otherDatasetId).addTask({
35
+ id: sourceEventId,
44
36
  name: "Explain a failed agent run",
45
37
  input: { request: "Summarize the report", outcome: "incomplete" },
46
- }, { idempotencyKey: "agent-run-456" });
38
+ });
47
39
  ```
48
40
 
49
- The Dataset ID is still included in every request. The helper binds it locally;
50
- it never uses a global account, last-selected Dataset or implicit Fix-it Model.
41
+ `addTask(datasetId, task)` and the `createTaskAdder` helper remain available.
51
42
 
52
- ## Input and receipt
43
+ ## Delivery, identity and retries
53
44
 
54
- Every task has a readable `name` and structured `input`. Optional fields are
55
- `expectedOutput`, `split`, `tags` and `source` (`kind`, `eventId`, `label`,
56
- `occurredAt`). The default split is `validation`. A `train` split alone does
57
- not authorize training. Keep a missing correct answer absent rather than
58
- replacing it with an error message.
45
+ A successful receipt confirms durable storage of the task and its processing
46
+ obligation. It does not wait for indexing, grading or training. Dataset projection
47
+ may lag acknowledgement. Await suspends this async function while the JavaScript
48
+ event loop continues handling other work. There is no SDK background queue,
49
+ flush command or permanent streaming connection.
59
50
 
60
- The complete request is bounded to 128 KiB. The receipt includes workspace,
61
- Dataset, task and operation identities, server `receivedAt`, saved revision,
62
- `state: "saved"` and `trainingUseAuthorized: false`. Source occurrence time is
63
- different from server upload time.
51
+ `id` identifies the source event within the workspace and Dataset. It is separate
52
+ from the server's canonical `receipt.taskId`. Repeating the same source ID and
53
+ content returns the original receipt; changed content conflicts instead of
54
+ silently overwriting. New credentials still need current authorization for that
55
+ exact Dataset. A successful upload grants no training permission.
64
56
 
65
- ## Delivery and retries
66
-
67
- Retain an explicit stable idempotency key for each source event. Repeating the
68
- same key and task in the same authorized destination returns the original
69
- receipt without inserting a duplicate. Reusing a key with different content is
70
- a conflict. Separate keys add separate tasks, including simultaneous writers.
71
-
72
- The SDK makes one request; it does not silently replay arbitrary mutations.
73
- When a response is lost, `DatasetTaskIngestionError.operationId` retains the
74
- operation identity and `outcomeUnknown` indicates that persistence could not be
75
- confirmed. Retry the original task with that same key. Do not generate a new
76
- key merely because the response was interrupted. A supplied abort signal is
77
- forwarded to the request; an interrupted response can still have been saved.
57
+ The SDK sends once. It never automatically replays a mutation. Preserve your
58
+ source ID and original content across uncertain outcomes:
78
59
 
79
60
  ```ts
80
61
  import { DatasetTaskIngestionError } from "openpond-sdk";
81
62
 
63
+ const task = { id: sourceEventId, name: "Expired token", input: { error: "expired" } };
82
64
  try {
83
- await addTask(task, { idempotencyKey: sourceEventId });
65
+ await openpond.addTask(task, { signal: request.signal, timeoutMs: 10_000 });
84
66
  } catch (error) {
85
67
  if (error instanceof DatasetTaskIngestionError && error.outcomeUnknown) {
86
- // Retain task + error.operationId privately for a later exact retry.
68
+ // Retain task and error.sourceId privately for a later exact retry.
87
69
  }
88
70
  throw error;
89
71
  }
90
72
  ```
91
73
 
92
- The underlying route is `POST /v1/dataset-workspaces/{datasetId}/tasks`, with
93
- `Authorization: Bearer …`, `X-OpenPond-Team-Id` and JSON:
94
-
95
- ```json
96
- {
97
- "schemaVersion": "openpond.datasetTaskAppend.v1",
98
- "operationId": "stable-source-event",
99
- "task": {
100
- "name": "Handle an expired API token",
101
- "input": { "observedError": "Token expired" }
102
- }
103
- }
104
- ```
105
-
106
- The server owns authorization, atomic append and revision history. Appending
107
- does not rewrite fixed Experiment Dataset versions or previously published
108
- releases. Full authoring still uses the revisioned Dataset workspace API.
74
+ When `id` is omitted the SDK generates one before dispatch. On uncertain delivery,
75
+ `DatasetTaskIngestionError.sourceId` (also `operationId`) retains it. Supply that
76
+ ID in a later retry. Prefer an explicit ID when cancellation or process exit could
77
+ prevent handling the error. An event never transmitted successfully cannot be
78
+ promised durable. `idempotencyKey` is accepted for existing callers; supplying a
79
+ conflicting `task.id` is rejected before dispatch.
80
+
81
+ Every task has `name` and structured `input`. Optional fields: `expectedOutput`,
82
+ `split`, `tags`, and `source` (`kind`, `eventId`, `label`, `occurredAt`). Default
83
+ split: `validation`; `train` alone grants no learning authority. Requests are
84
+ bounded to 128 KiB. The receipt retains workspace, Dataset, canonical task and
85
+ source identities, first-save `receivedAt`, revision, `state: "saved"` and
86
+ `trainingUseAuthorized: false`. Source occurrence time differs from upload time.
87
+ Existing immutable releases and Experiment inputs retain their own identities.
109
88
 
110
89
  ## Python
111
90
 
112
- The maintained `ponder-sdk` package exposes the same operation on `Client`
113
- and `AsyncClient`:
91
+ `ponder-sdk` uses the same configured destination and source ID with its native
92
+ owned connection pool:
114
93
 
115
94
  ```python
116
95
  from ponder_sdk import Client
117
96
 
118
- with Client(base_url=api_origin, api_key=api_key, team_id=team_id) as client:
119
- receipt = client.add_task(
120
- dataset_id,
121
- {"name": "Handle an expired API token", "input": {"error": "Token expired"}},
122
- idempotency_key=source_event_id,
123
- )
97
+ with Client(base_url=api_origin, api_key=api_key, team_id=team_id,
98
+ dataset_id=dataset_id) as openpond:
99
+ receipt = openpond.add_task({
100
+ "id": source_event_id,
101
+ "name": "Expired token",
102
+ "input": {"error": "Token expired"},
103
+ })
104
+ # Alternate destination; uses the same pool:
105
+ # openpond.dataset(other_dataset_id).add_task(task)
124
106
  ```
125
107
 
126
- Use `async with AsyncClient(...)` and `await client.add_task(...)` for async
127
- applications. Both use the existing client connection pool and typed wire
128
- validation. Retain the caller's key/task across an unknown mutation outcome.
108
+ Sync calls block their calling thread. Async applications use
109
+ `async with AsyncClient(...)` and `await openpond.add_task(task)`. Both validate the
110
+ maintained wire schemas and never replay writes automatically. A `PonderError`
111
+ retains `operation_id` and `outcome_unknown`; cancellation propagates, so retain
112
+ your own source ID before awaiting. Closing the HTTP pool is resource cleanup,
113
+ not delivery flushing.
114
+
115
+ ## HTTP and React
116
+
117
+ The HTTP operation remains `POST /v1/dataset-workspaces/{datasetId}/tasks` with
118
+ Bearer auth and `X-OpenPond-Team-Id`. The public `task.id` maps to wire
119
+ `operationId`; it is excluded from `task` content:
120
+
121
+ ```json
122
+ {
123
+ "schemaVersion": "openpond.datasetTaskAppend.v1",
124
+ "operationId": "stable-source-event",
125
+ "task": { "name": "Expired token", "input": { "error": "expired" } }
126
+ }
127
+ ```
129
128
 
130
- ## OpenTelemetry
129
+ React calls your authenticated product backend. The backend selects an authorized
130
+ Dataset and awaits the configured client. Keep OpenPond secrets and destination
131
+ authority server-side; do not accept an arbitrary browser-supplied Dataset ID.
132
+ For named model calls and the optional Vercel integration see
133
+ [product integration](PRODUCT_INTEGRATION.md).
131
134
 
132
- OpenTelemetry is an optional source adapter, not this HTTP request format.
133
- Its span exporter can project explicitly selected spans into structured tasks
134
- using the same destination and receipts. It does not make this endpoint an
135
- OTLP receiver, automatically capture arbitrary logs, or authorize training.
135
+ OpenTelemetry is a separate optional source adapter, not this request format.
136
+ Project explicitly selected spans into structured tasks. This endpoint is not an
137
+ OTLP receiver and does not capture arbitrary logs or authorize training.
@@ -50,8 +50,8 @@ GET /v1/training/jobs?modelProjectId=&cursor=&limit=
50
50
  GET /v1/training/jobs/{jobId}
51
51
  POST /v1/training/jobs/{jobId}/cancel
52
52
  POST /v1/training/jobs/{jobId}/stop-after-group
53
- GET /v1/training/jobs/{jobId}/events
54
- GET /v1/training/jobs/{jobId}/logs
53
+ GET /v1/training/jobs/{jobId}/events?after=0&limit=50
54
+ GET /v1/training/jobs/{jobId}/logs?after=0&limit=50
55
55
  GET /v1/training/jobs/{jobId}/outputs
56
56
 
57
57
  PUT /v1/model-projects/{portableProjectId}
@@ -328,3 +328,7 @@ It performs no authentication, network upload, tool execution, training, or mode
328
328
  activation. Callers must verify authentic durable hosted receipts before
329
329
  acknowledging delivery. Import the portable entry in browser code; the Node queue
330
330
  is a separate runtime capability.
331
+
332
+ ### Retained evidence pagination
333
+
334
+ Events and logs return at most 100 records per database-selected page (default 50), plus `nextOffset` or `null`. Use `training.eventsPage(jobId, { after, limit })` and `training.logsPage(...)` to obtain `{ items, nextOffset }`. The SDK validates the returned job, sequence window, count and continuation before exposing records. Offsets address the current ordered snapshot; lifecycle snapshots can change while a run is live. They are not an immutable append-only cursor. Existing `events()`/`logs()` convenience reads follow pages with an aggregate response-byte budget and a 100,000-record ceiling. MCP requests exactly one page.
@@ -653,3 +653,19 @@ evaluation remains an independent candidate check. A nightly schedule uses the
653
653
  authored time and timezone, waits for approved unused examples, and respects the
654
654
  saved task threshold and spend limits. It does not itself enable conversation
655
655
  collection, automatic data approval, or automatic serving.
656
+
657
+
658
+ Selective neuron continuation uses an exact compatible saved neuron version.
659
+ `neuronTrainingFormFromModelVersion` retains that version and its cumulative
660
+ patch, copies the parent's selection configuration, clears the target choice,
661
+ and requires reviewed new target data. `background=[]` retains the original
662
+ selection as inherited evidence; it does not report selection on the new data.
663
+ The explicit mode is `frozen_parent_fresh_residual_adam_v1`: the parent stays
664
+ frozen, fresh zero column parameters use the original dense base as their
665
+ anchors, and a fresh Adam optimizer learns a residual. No optimizer or RNG
666
+ checkpoint is resumed. Optional preservation compares against the starting
667
+ base plus parent patch. The worker verifies the exact parent bytes before
668
+ optimizer creation and exports a cumulative patch with parent, initial and
669
+ final state hashes. A runtime must explicitly advertise this mode before
670
+ admission; source packaging alone does not establish native qualification or
671
+ old/new held-out retention.
@@ -12,12 +12,12 @@ import {
12
12
  sealAdvancedRefinerEvaluationPin,
13
13
  sealAdvancedRefinerReviewReceipt,
14
14
  verifyAdvancedRefinerEvaluationPin
15
- } from "./chunk-6EZAGTZG.js";
16
- import "./chunk-3B5KSH3V.js";
17
- import "./chunk-H3367ISL.js";
15
+ } from "./chunk-RN5MK7NH.js";
18
16
  import "./chunk-QNOCM5JX.js";
17
+ import "./chunk-3B5KSH3V.js";
19
18
  import "./chunk-626JLH4D.js";
20
19
  import "./chunk-O6FKHAWN.js";
20
+ import "./chunk-H3367ISL.js";
21
21
  import "./chunk-GS5HEE4M.js";
22
22
  import "./chunk-ZRTNR4U7.js";
23
23
  import "./chunk-JMFYNIYZ.js";
package/dist/ai-sdk.js ADDED
@@ -0,0 +1,224 @@
1
+ import {
2
+ ProductModelError,
3
+ productModelEndpoint
4
+ } from "./chunk-BRP3EBCR.js";
5
+ import "./chunk-B3ZHJ2KK.js";
6
+ import "./chunk-YLJ4XMA6.js";
7
+
8
+ // src/ai-sdk.ts
9
+ import {
10
+ createOpenAICompatible
11
+ } from "@ai-sdk/openai-compatible";
12
+ import { z } from "zod";
13
+
14
+ // src/product-model-response.ts
15
+ function boundProductModelResponse(response) {
16
+ if (!response.body) return response;
17
+ const streaming = response.headers.get("content-type")?.includes("text/event-stream") ?? false;
18
+ const reader = response.body.getReader();
19
+ const decoder = new TextDecoder();
20
+ let total = 0, pending = "", terminal = false;
21
+ let identity;
22
+ const fail = () => new ProductModelError(
23
+ streaming ? "model_stream_interrupted" : "model_response_invalid",
24
+ response.status,
25
+ true,
26
+ response.headers.get("x-openpond-request-id") ?? response.headers.get("x-request-id") ?? void 0
27
+ );
28
+ const body = new ReadableStream({
29
+ async pull(controller) {
30
+ try {
31
+ const { done, value } = await reader.read();
32
+ if (done) {
33
+ if (streaming && !terminal) throw fail();
34
+ controller.close();
35
+ reader.releaseLock();
36
+ return;
37
+ }
38
+ total += value.byteLength;
39
+ if (total > 8 * 1024 * 1024) throw fail();
40
+ if (streaming) {
41
+ pending = (pending + decoder.decode(value, { stream: true })).replace(/\r\n/g, "\n");
42
+ let boundary;
43
+ while ((boundary = pending.indexOf("\n\n")) >= 0) {
44
+ const frame = pending.slice(0, boundary);
45
+ pending = pending.slice(boundary + 2);
46
+ if (new TextEncoder().encode(frame).length > 1024 * 1024) throw fail();
47
+ const data = frame.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
48
+ if (data === "[DONE]") terminal = true;
49
+ else if (data) {
50
+ const chunk = JSON.parse(data);
51
+ if (typeof chunk.id === "string" && typeof chunk.model === "string") {
52
+ if (identity && (chunk.id !== identity.id || chunk.model !== identity.model))
53
+ throw fail();
54
+ identity = { id: chunk.id, model: chunk.model };
55
+ }
56
+ }
57
+ }
58
+ if (new TextEncoder().encode(pending).length > 1024 * 1024) throw fail();
59
+ }
60
+ controller.enqueue(value);
61
+ } catch {
62
+ await reader.cancel().catch(() => {
63
+ });
64
+ controller.error(fail());
65
+ }
66
+ },
67
+ async cancel(reason) {
68
+ await reader.cancel(reason);
69
+ }
70
+ });
71
+ return new Response(body, {
72
+ status: response.status,
73
+ statusText: response.statusText,
74
+ headers: response.headers
75
+ });
76
+ }
77
+
78
+ // src/ai-sdk.ts
79
+ var Evidence = z.object({ openpond: z.record(z.string(), z.json()) }).passthrough();
80
+ function metadata(raw) {
81
+ const value = Evidence.safeParse(raw);
82
+ return value.success ? { openpond: value.data.openpond } : void 0;
83
+ }
84
+ function toAIModel(model) {
85
+ const connection = model.providerConnection();
86
+ const provider = createOpenAICompatible({
87
+ name: "openpond",
88
+ baseURL: productModelEndpoint(connection.baseUrl),
89
+ apiKey: connection.apiKey,
90
+ // The personalized gateway always supplies terminal usage and rejects
91
+ // OpenAI's optional stream_options field.
92
+ includeUsage: false,
93
+ headers: connection.teamId ? { "X-OpenPond-Team-Id": connection.teamId } : void 0,
94
+ transformRequestBody: (raw) => {
95
+ const body = JSON.parse(JSON.stringify(raw));
96
+ if (body.model !== model.id)
97
+ throw new ProductModelError("model_override_forbidden", 0, false);
98
+ const allowed = /* @__PURE__ */ new Set([
99
+ "model",
100
+ "messages",
101
+ "stream",
102
+ "max_tokens",
103
+ "temperature",
104
+ "top_p",
105
+ "seed",
106
+ "stop",
107
+ "tools",
108
+ "tool_choice"
109
+ ]);
110
+ if (Object.keys(body).some((key) => !allowed.has(key)))
111
+ throw new ProductModelError("parameter_unsupported", 0, false);
112
+ return body;
113
+ },
114
+ metadataExtractor: {
115
+ async extractMetadata({ parsedBody }) {
116
+ return metadata(parsedBody);
117
+ },
118
+ createStreamExtractor() {
119
+ let evidence;
120
+ return {
121
+ processChunk(chunk) {
122
+ evidence = metadata(chunk) ?? evidence;
123
+ },
124
+ buildMetadata() {
125
+ return evidence;
126
+ }
127
+ };
128
+ }
129
+ },
130
+ supportsStructuredOutputs: model.capabilities.structuredOutput,
131
+ fetch: async (url, init) => {
132
+ const timeout = AbortSignal.timeout(connection.timeoutMs ?? 6e4);
133
+ const headers = new Headers(init?.headers);
134
+ headers.set("Authorization", `Bearer ${connection.apiKey}`);
135
+ if (connection.teamId) headers.set("X-OpenPond-Team-Id", connection.teamId);
136
+ return boundProductModelResponse(
137
+ await (connection.fetch ?? fetch)(url, {
138
+ ...init,
139
+ redirect: "error",
140
+ headers,
141
+ signal: init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout
142
+ })
143
+ );
144
+ }
145
+ }).chatModel(model.id);
146
+ const check = (options) => {
147
+ const rawOptions = options.providerOptions?.openpond;
148
+ if (rawOptions && ["stream", "stream_options"].some((key) => key in rawOptions))
149
+ throw new ProductModelError("transport_override_forbidden", 0, false);
150
+ if (options.tools?.length && !model.capabilities.tools)
151
+ throw new ProductModelError("tools_unsupported", 0, false);
152
+ if (options.responseFormat?.type === "json")
153
+ throw new ProductModelError("structured_output_unsupported", 0, false);
154
+ if (options.prompt.some(
155
+ (message) => message.role === "tool" && !model.capabilities.tools || message.role !== "system" && message.content.some(
156
+ (part) => part.type !== "text" && !(model.capabilities.tools && ["tool-call", "tool-result"].includes(part.type))
157
+ )
158
+ ))
159
+ throw new ProductModelError("input_capability_unsupported", 0, false);
160
+ };
161
+ const normalizeError = (error) => {
162
+ if (error instanceof ProductModelError) return error;
163
+ if (error && typeof error === "object" && "cause" in error && error.cause instanceof ProductModelError)
164
+ return error.cause;
165
+ const status = error && typeof error === "object" && "statusCode" in error && typeof error.statusCode === "number" ? error.statusCode : 0;
166
+ const unknown = status === 0 || status === 408 || status >= 500 || status >= 200 && status < 300;
167
+ return new ProductModelError(
168
+ status >= 400 ? "model_request_rejected" : "model_outcome_unknown",
169
+ status,
170
+ unknown
171
+ );
172
+ };
173
+ return {
174
+ specificationVersion: provider.specificationVersion,
175
+ provider: provider.provider,
176
+ modelId: provider.modelId,
177
+ supportedUrls: provider.supportedUrls,
178
+ async doGenerate(options) {
179
+ check(options);
180
+ try {
181
+ return await provider.doGenerate(options);
182
+ } catch (error) {
183
+ throw normalizeError(error);
184
+ }
185
+ },
186
+ async doStream(options) {
187
+ check(options);
188
+ if (!model.capabilities.streaming)
189
+ throw new ProductModelError("streaming_unsupported", 0, false);
190
+ try {
191
+ const result = await provider.doStream(options);
192
+ const reader = result.stream.getReader();
193
+ return {
194
+ ...result,
195
+ stream: new ReadableStream({
196
+ async pull(controller) {
197
+ try {
198
+ const { done, value } = await reader.read();
199
+ if (done) {
200
+ controller.close();
201
+ reader.releaseLock();
202
+ } else
203
+ controller.enqueue(
204
+ value.type === "error" ? { ...value, error: normalizeError(value.error) } : value
205
+ );
206
+ } catch (error) {
207
+ controller.error(normalizeError(error));
208
+ }
209
+ },
210
+ async cancel(reason) {
211
+ await reader.cancel(reason);
212
+ }
213
+ })
214
+ };
215
+ } catch (error) {
216
+ throw normalizeError(error);
217
+ }
218
+ }
219
+ };
220
+ }
221
+ export {
222
+ toAIModel
223
+ };
224
+ //# sourceMappingURL=ai-sdk.js.map