@aexhq/sdk 0.13.6

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 (112) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +160 -0
  3. package/dist/_contracts/connection-ticket.d.ts +21 -0
  4. package/dist/_contracts/connection-ticket.js +49 -0
  5. package/dist/_contracts/event-envelope.d.ts +276 -0
  6. package/dist/_contracts/event-envelope.js +324 -0
  7. package/dist/_contracts/event-stream-client.d.ts +47 -0
  8. package/dist/_contracts/event-stream-client.js +141 -0
  9. package/dist/_contracts/http.d.ts +35 -0
  10. package/dist/_contracts/http.js +114 -0
  11. package/dist/_contracts/index.d.ts +28 -0
  12. package/dist/_contracts/index.js +29 -0
  13. package/dist/_contracts/managed-key.d.ts +74 -0
  14. package/dist/_contracts/managed-key.js +110 -0
  15. package/dist/_contracts/operations.d.ts +237 -0
  16. package/dist/_contracts/operations.js +632 -0
  17. package/dist/_contracts/provider-support.d.ts +220 -0
  18. package/dist/_contracts/provider-support.js +90 -0
  19. package/dist/_contracts/proxy-protocol.d.ts +257 -0
  20. package/dist/_contracts/proxy-protocol.js +234 -0
  21. package/dist/_contracts/proxy-validation.d.ts +19 -0
  22. package/dist/_contracts/proxy-validation.js +51 -0
  23. package/dist/_contracts/run-artifacts.d.ts +47 -0
  24. package/dist/_contracts/run-artifacts.js +101 -0
  25. package/dist/_contracts/run-config.d.ts +304 -0
  26. package/dist/_contracts/run-config.js +659 -0
  27. package/dist/_contracts/run-cost.d.ts +125 -0
  28. package/dist/_contracts/run-cost.js +616 -0
  29. package/dist/_contracts/run-custody.d.ts +226 -0
  30. package/dist/_contracts/run-custody.js +465 -0
  31. package/dist/_contracts/run-record.d.ts +127 -0
  32. package/dist/_contracts/run-record.js +177 -0
  33. package/dist/_contracts/run-retention.d.ts +213 -0
  34. package/dist/_contracts/run-retention.js +484 -0
  35. package/dist/_contracts/run-unit.d.ts +194 -0
  36. package/dist/_contracts/run-unit.js +215 -0
  37. package/dist/_contracts/runner-event.d.ts +114 -0
  38. package/dist/_contracts/runner-event.js +187 -0
  39. package/dist/_contracts/runtime-manifest.d.ts +106 -0
  40. package/dist/_contracts/runtime-manifest.js +98 -0
  41. package/dist/_contracts/runtime-security-profile.d.ts +27 -0
  42. package/dist/_contracts/runtime-security-profile.js +82 -0
  43. package/dist/_contracts/runtime-sizes.d.ts +144 -0
  44. package/dist/_contracts/runtime-sizes.js +136 -0
  45. package/dist/_contracts/runtime-types.d.ts +212 -0
  46. package/dist/_contracts/runtime-types.js +2 -0
  47. package/dist/_contracts/sdk-errors.d.ts +34 -0
  48. package/dist/_contracts/sdk-errors.js +52 -0
  49. package/dist/_contracts/sdk-secrets.d.ts +31 -0
  50. package/dist/_contracts/sdk-secrets.js +220 -0
  51. package/dist/_contracts/side-effect-audit.d.ts +129 -0
  52. package/dist/_contracts/side-effect-audit.js +494 -0
  53. package/dist/_contracts/sse.d.ts +74 -0
  54. package/dist/_contracts/sse.js +0 -0
  55. package/dist/_contracts/stable.d.ts +26 -0
  56. package/dist/_contracts/stable.js +44 -0
  57. package/dist/_contracts/status.d.ts +19 -0
  58. package/dist/_contracts/status.js +61 -0
  59. package/dist/_contracts/submission.d.ts +383 -0
  60. package/dist/_contracts/submission.js +1380 -0
  61. package/dist/agents-md.d.ts +46 -0
  62. package/dist/agents-md.js +83 -0
  63. package/dist/agents-md.js.map +1 -0
  64. package/dist/asset-upload.d.ts +66 -0
  65. package/dist/asset-upload.js +168 -0
  66. package/dist/asset-upload.js.map +1 -0
  67. package/dist/bundle.d.ts +33 -0
  68. package/dist/bundle.js +89 -0
  69. package/dist/bundle.js.map +1 -0
  70. package/dist/cli.mjs +4140 -0
  71. package/dist/cli.mjs.sha256 +1 -0
  72. package/dist/client.d.ts +460 -0
  73. package/dist/client.js +857 -0
  74. package/dist/client.js.map +1 -0
  75. package/dist/fetch-archive.d.ts +16 -0
  76. package/dist/fetch-archive.js +170 -0
  77. package/dist/fetch-archive.js.map +1 -0
  78. package/dist/file.d.ts +57 -0
  79. package/dist/file.js +153 -0
  80. package/dist/file.js.map +1 -0
  81. package/dist/index.d.ts +30 -0
  82. package/dist/index.js +34 -0
  83. package/dist/index.js.map +1 -0
  84. package/dist/mcp-server.d.ts +84 -0
  85. package/dist/mcp-server.js +114 -0
  86. package/dist/mcp-server.js.map +1 -0
  87. package/dist/node-fs.d.ts +12 -0
  88. package/dist/node-fs.js +44 -0
  89. package/dist/node-fs.js.map +1 -0
  90. package/dist/proxy-endpoint.d.ts +131 -0
  91. package/dist/proxy-endpoint.js +147 -0
  92. package/dist/proxy-endpoint.js.map +1 -0
  93. package/dist/skill.d.ts +117 -0
  94. package/dist/skill.js +169 -0
  95. package/dist/skill.js.map +1 -0
  96. package/dist/version.d.ts +9 -0
  97. package/dist/version.js +10 -0
  98. package/dist/version.js.map +1 -0
  99. package/docs/cleanup.md +38 -0
  100. package/docs/credentials.md +153 -0
  101. package/docs/events.md +76 -0
  102. package/docs/mcp.md +47 -0
  103. package/docs/outputs.md +157 -0
  104. package/docs/product-boundaries.md +57 -0
  105. package/docs/provider-runtime-capabilities.md +103 -0
  106. package/docs/quickstart.md +110 -0
  107. package/docs/release.md +99 -0
  108. package/docs/run-config.md +53 -0
  109. package/docs/run-record.md +39 -0
  110. package/docs/skills.md +139 -0
  111. package/docs/testing.md +29 -0
  112. package/package.json +47 -0
@@ -0,0 +1,632 @@
1
+ import { strToU8, zipSync } from "fflate";
2
+ import { RunStateError } from "./sdk-errors.js";
3
+ import { assertRunRecordArchivePublicSafeV1, buildRunRecordDownloadManifestV1 } from "./run-record.js";
4
+ import { runArtifactRel } from "./run-artifacts.js";
5
+ /**
6
+ * The single source of truth for SDK<->BFF transport. The SDK class
7
+ * AND the CLI subcommands both call these functions; neither
8
+ * surface re-implements HTTP requests against the dashboard.
9
+ *
10
+ * Every function takes an HttpClient (so callers control auth + fetch
11
+ * injection) and returns parsed responses.
12
+ *
13
+ * Workspace identity is derived server-side from the API token on
14
+ * every request; callers do not pass `workspaceId`.
15
+ */
16
+ export async function getRun(http, runId) {
17
+ const result = await http.request(`/api/runs/${encodeURIComponent(runId)}`);
18
+ return hasRun(result) ? result.run : result;
19
+ }
20
+ /**
21
+ * Strongly-typed accessor for the full self-contained run unit:
22
+ * parsed submission inputs, attempts, indexed events (with
23
+ * pagination cursor for large runs), raw-event Storage manifest,
24
+ * outputs, capture failures, proxy-call audit, pinned skills,
25
+ * provider skills, inline skills.
26
+ *
27
+ * Backed by the same `GET /api/runs/:runId` endpoint that
28
+ * `getRun` calls; this variant just narrows the return type to
29
+ * the documented wire shape. Prefer this for new code; `getRun`
30
+ * stays for callers that only need the loose record.
31
+ */
32
+ export async function getRunUnit(http, runId) {
33
+ return http.request(`/api/runs/${encodeURIComponent(runId)}`);
34
+ }
35
+ export async function listRunEvents(http, runId, options = {}) {
36
+ const query = options.channel && options.channel !== "event"
37
+ ? { channel: options.channel }
38
+ : {};
39
+ const result = await http.request(`/api/runs/${encodeURIComponent(runId)}/events`, {}, query);
40
+ return result.events;
41
+ }
42
+ /**
43
+ * Mint a short-lived coordinator WS ticket via the workspace-token-gated
44
+ * broker (`/api/runs/:id/events/ticket`). The returned `wsUrl` + `ticket`
45
+ * open the live event stream directly against the coordinator. Throws if no
46
+ * coordinator is configured for the deployment (HTTP 503).
47
+ */
48
+ export async function getCoordinatorTicket(http, runId) {
49
+ return http.request(`/api/runs/${encodeURIComponent(runId)}/events/ticket`, { method: "POST" });
50
+ }
51
+ export async function listOutputs(http, runId) {
52
+ const result = await http.request(`/api/runs/${encodeURIComponent(runId)}/outputs`);
53
+ return result.outputs;
54
+ }
55
+ /**
56
+ * List the run's platform diagnostics (the `logs` namespace). Legacy stored
57
+ * filenames are normalized to canonical public namespaces.
58
+ */
59
+ export async function listLogs(http, runId) {
60
+ const result = await http.request(`/api/runs/${encodeURIComponent(runId)}/logs`);
61
+ return result.logs.map((log) => typeof log.filename === "string" ? { ...log, filename: runArtifactRel(log.filename) } : log);
62
+ }
63
+ export async function createOutputLink(http, runId, outputId) {
64
+ return http.request(`/api/runs/${encodeURIComponent(runId)}/outputs/${encodeURIComponent(outputId)}/link`, { method: "POST" });
65
+ }
66
+ export function resolveOutputFileSelector(outputs, selector, runId) {
67
+ if (isPathSelector(selector)) {
68
+ const target = normalizeOutputLookupPath(selector.path);
69
+ if (!target) {
70
+ throw new RunStateError("downloadOutput: output path must be non-empty", { runId, path: selector.path });
71
+ }
72
+ const matches = outputs.filter((output) => {
73
+ if (typeof output.filename !== "string")
74
+ return false;
75
+ const filename = normalizeOutputLookupPath(output.filename);
76
+ if (selector.match === "suffix") {
77
+ return filename === target || filename.endsWith(`/${target}`);
78
+ }
79
+ return filename === target;
80
+ });
81
+ if (matches.length === 1)
82
+ return matches[0];
83
+ if (matches.length > 1) {
84
+ throw new RunStateError(`downloadOutput: output path "${selector.path}" matched multiple files`, { runId, path: selector.path, matches: matches.map((output) => output.filename ?? output.id) });
85
+ }
86
+ throw new RunStateError(`downloadOutput: output path "${selector.path}" was not found`, {
87
+ runId,
88
+ path: selector.path
89
+ });
90
+ }
91
+ if (typeof selector?.id !== "string" || selector.id.length === 0) {
92
+ throw new RunStateError("downloadOutput: selector must include an output id or path", { runId });
93
+ }
94
+ return { ...selector, id: selector.id };
95
+ }
96
+ export async function downloadOutput(http, runId, selector) {
97
+ const output = isPathSelector(selector)
98
+ ? resolveOutputFileSelector(await listOutputs(http, runId), selector, runId)
99
+ : resolveOutputFileSelector([], selector, runId);
100
+ const { response } = await http.download(`/api/runs/${encodeURIComponent(runId)}/outputs/${encodeURIComponent(output.id)}/download`);
101
+ return { output, bytes: new Uint8Array(await response.arrayBuffer()) };
102
+ }
103
+ export async function cancelRun(http, runId) {
104
+ await http.request(`/api/runs/${encodeURIComponent(runId)}/cancel`, { method: "POST" });
105
+ }
106
+ export async function deleteRun(http, runId) {
107
+ await http.request(`/api/runs/${encodeURIComponent(runId)}`, { method: "DELETE" });
108
+ }
109
+ /**
110
+ * Delete a workspace asset cache entry. Accepts an `asset_<id>` value,
111
+ * `sha256:<hex>`, or a bare 64-hex digest. Workspace is derived server-side
112
+ * from the token; idempotent.
113
+ * Does NOT affect runs that already snapshotted the asset.
114
+ */
115
+ export async function deleteWorkspaceAsset(http, hash) {
116
+ const assetId = hash.startsWith("asset_")
117
+ ? hash
118
+ : `asset_${hash.startsWith("sha256:") ? hash.slice("sha256:".length) : hash}`;
119
+ await http.request(`/assets/${encodeURIComponent(assetId)}`, { method: "DELETE" });
120
+ }
121
+ export async function whoami(http) {
122
+ return http.request("/api/whoami");
123
+ }
124
+ /**
125
+ * Download each artifact's bytes into a zip-file map keyed by
126
+ * `<zipPrefix><relative-path>`, fetched from the `outputs` or `logs`
127
+ * download route. Best-effort: a per-artifact fetch failure records an
128
+ * `errors[]` entry rather than aborting the rest, so the failure is
129
+ * surfaced (never silent) while a partially-available run still yields a
130
+ * usable zip.
131
+ */
132
+ async function collectArtifactBytes(http, runId, items, zipPrefix, namespace) {
133
+ const entries = [];
134
+ const captured = [];
135
+ const errors = [];
136
+ for (const item of items) {
137
+ const rel = item.filename ?? item.id;
138
+ try {
139
+ const { response } = await http.download(`/api/runs/${encodeURIComponent(runId)}/${namespace}/${encodeURIComponent(item.id)}/download`);
140
+ entries.push({
141
+ path: `${zipPrefix}${rel}`,
142
+ bytes: new Uint8Array(await response.arrayBuffer()),
143
+ ...(item.contentType !== undefined ? { contentType: item.contentType } : {}),
144
+ ...(namespace === "outputs" ? { customerContent: true } : {})
145
+ });
146
+ captured.push({
147
+ id: item.id,
148
+ filename: item.filename ?? null,
149
+ ...(item.sizeBytes !== undefined ? { sizeBytes: item.sizeBytes } : {}),
150
+ ...(item.contentType !== undefined ? { contentType: item.contentType } : {})
151
+ });
152
+ }
153
+ catch (err) {
154
+ errors.push({ namespace, id: item.id, filename: item.filename ?? null, message: err.message });
155
+ }
156
+ }
157
+ return { entries: Object.freeze(entries), captured, errors };
158
+ }
159
+ function eventsJsonl(events) {
160
+ return strToU8(events.map((event) => JSON.stringify(event)).join("\n"));
161
+ }
162
+ async function tryListOptionalRunEvents(http, runId, channel) {
163
+ try {
164
+ const events = await listRunEvents(http, runId, { channel });
165
+ if (channel === "log") {
166
+ return events.length > 0 && events.every(isLogChannelEvent)
167
+ ? { status: "present", events }
168
+ : { status: "unavailable", events: [] };
169
+ }
170
+ return hasUnifiedStreamEvidence(events)
171
+ ? { status: "present", events }
172
+ : { status: "unavailable", events: [] };
173
+ }
174
+ catch {
175
+ return { status: "unavailable", events: [] };
176
+ }
177
+ }
178
+ function isLogChannelEvent(event) {
179
+ return event.channel === "log";
180
+ }
181
+ function hasUnifiedStreamEvidence(events) {
182
+ return events.some((event) => event.channel === "log" || event.channel === "event");
183
+ }
184
+ function isPathSelector(selector) {
185
+ return Boolean(selector && typeof selector === "object" && "path" in selector);
186
+ }
187
+ function normalizeOutputLookupPath(path) {
188
+ return path.replace(/\\/g, "/").replace(/^\/+/, "");
189
+ }
190
+ /**
191
+ * Download EVERYTHING about a run as one zip, organised into the four
192
+ * namespace folders:
193
+ *
194
+ * metadata/run.json — the run record.
195
+ * events/events.jsonl — typed event-channel records.
196
+ * events/logs.jsonl — log-channel records, when the API serves them.
197
+ * events/all.jsonl — full unified stream, when the API serves it.
198
+ * outputs/<rel> — the run's deliverables.
199
+ * logs/<rel> — platform diagnostics.
200
+ * manifest.json — `RunRecordManifestV1`.
201
+ */
202
+ export async function download(http, runId) {
203
+ const [run, events, logEvents, allEvents, outputs, logItems] = await Promise.all([
204
+ getRun(http, runId),
205
+ listRunEvents(http, runId),
206
+ tryListOptionalRunEvents(http, runId, "log"),
207
+ tryListOptionalRunEvents(http, runId, "all"),
208
+ listOutputs(http, runId),
209
+ listLogs(http, runId)
210
+ ]);
211
+ const out = await collectArtifactBytes(http, runId, outputs, "outputs/", "outputs");
212
+ const logs = await collectArtifactBytes(http, runId, logItems, "logs/", "logs");
213
+ const submissionSnapshot = extractSubmissionSnapshot(run);
214
+ const costTelemetry = extractCostTelemetry(run);
215
+ const manifest = buildRunRecordDownloadManifestV1({
216
+ runId,
217
+ outputs: out.captured,
218
+ logs: logs.captured,
219
+ errors: [...out.errors, ...logs.errors],
220
+ typedEventCount: events.length,
221
+ ...(submissionSnapshot ? { submission: { status: "present" } } : {}),
222
+ ...(costTelemetry ? { cost: { status: "present" } } : {}),
223
+ logEvents: { status: logEvents.status, recordCount: logEvents.events.length },
224
+ allEvents: { status: allEvents.status, recordCount: allEvents.events.length }
225
+ });
226
+ return zipEntries([
227
+ jsonEntry("metadata/run.json", run),
228
+ ...(submissionSnapshot ? [jsonEntry("metadata/submission.json", submissionSnapshot)] : []),
229
+ ...(costTelemetry ? [jsonEntry("metadata/cost.json", costTelemetry)] : []),
230
+ jsonlEntry("events/events.jsonl", events),
231
+ ...(logEvents.status === "present" ? [jsonlEntry("events/logs.jsonl", logEvents.events)] : []),
232
+ ...(allEvents.status === "present" ? [jsonlEntry("events/all.jsonl", allEvents.events)] : []),
233
+ ...out.entries,
234
+ ...logs.entries,
235
+ jsonEntry("manifest.json", manifest)
236
+ ]);
237
+ }
238
+ /**
239
+ * Download only the run's deliverables (the `outputs` namespace). Zip
240
+ * layout: `<rel>` per file plus a `manifest.json`
241
+ * (`{ runId, namespace: "outputs", outputs[], errors[] }`).
242
+ */
243
+ export async function downloadOutputs(http, runId) {
244
+ const outputs = await listOutputs(http, runId);
245
+ const { entries, captured, errors } = await collectArtifactBytes(http, runId, outputs, "", "outputs");
246
+ return zipEntries([
247
+ ...entries,
248
+ jsonEntry("manifest.json", { runId, namespace: "outputs", outputs: captured, errors })
249
+ ]);
250
+ }
251
+ /**
252
+ * Download only the platform diagnostics (the `logs` namespace). Zip
253
+ * layout: `<rel>` per file plus a `manifest.json`
254
+ * (`{ runId, namespace: "logs", logs[], errors[] }`).
255
+ */
256
+ export async function downloadLogs(http, runId) {
257
+ const logItems = await listLogs(http, runId);
258
+ const { entries, captured, errors } = await collectArtifactBytes(http, runId, logItems, "", "logs");
259
+ return zipEntries([
260
+ ...entries,
261
+ jsonEntry("manifest.json", { runId, namespace: "logs", logs: captured, errors })
262
+ ]);
263
+ }
264
+ /**
265
+ * Download only the event archive (the `events` namespace). Always includes
266
+ * typed `events.jsonl`; includes `logs.jsonl` / `all.jsonl` when the deployed
267
+ * event API proves those channel exports are available.
268
+ */
269
+ export async function downloadEvents(http, runId) {
270
+ const [events, logEvents, allEvents] = await Promise.all([
271
+ listRunEvents(http, runId),
272
+ tryListOptionalRunEvents(http, runId, "log"),
273
+ tryListOptionalRunEvents(http, runId, "all")
274
+ ]);
275
+ return zipEntries([
276
+ jsonlEntry("events.jsonl", events),
277
+ ...(logEvents.status === "present" ? [jsonlEntry("logs.jsonl", logEvents.events)] : []),
278
+ ...(allEvents.status === "present" ? [jsonlEntry("all.jsonl", allEvents.events)] : [])
279
+ ]);
280
+ }
281
+ /**
282
+ * Download only the run record (the `metadata` namespace) as a zip
283
+ * containing `run.json`.
284
+ */
285
+ export async function downloadMetadata(http, runId) {
286
+ const run = await getRun(http, runId);
287
+ return zipEntries([jsonEntry("run.json", run)]);
288
+ }
289
+ function zipEntries(entries) {
290
+ assertRunRecordArchivePublicSafeV1(entries);
291
+ const files = {};
292
+ for (const entry of entries) {
293
+ files[entry.path] = entry.bytes;
294
+ }
295
+ return zipSync(files);
296
+ }
297
+ function jsonEntry(path, value) {
298
+ return {
299
+ path,
300
+ bytes: strToU8(JSON.stringify(value, null, 2)),
301
+ contentType: "application/json; charset=utf-8"
302
+ };
303
+ }
304
+ function jsonlEntry(path, events) {
305
+ return {
306
+ path,
307
+ bytes: eventsJsonl(events),
308
+ contentType: "application/jsonl; charset=utf-8"
309
+ };
310
+ }
311
+ function extractSubmissionSnapshot(run) {
312
+ const raw = run.submission;
313
+ if (!isRecord(raw) || raw.kind !== "submission" || !isRecord(raw.submission)) {
314
+ return undefined;
315
+ }
316
+ return {
317
+ submission: raw.submission
318
+ };
319
+ }
320
+ function extractCostTelemetry(run) {
321
+ const raw = run.costTelemetry;
322
+ return isRecord(raw) ? raw : undefined;
323
+ }
324
+ function isRecord(value) {
325
+ return typeof value === "object" && value !== null && !Array.isArray(value);
326
+ }
327
+ // ===========================================================================
328
+ // Run submission operations (Skill / McpServer / run config composition)
329
+ // ===========================================================================
330
+ export async function submitRun(http, request) {
331
+ return http.request("/api/runs", {
332
+ method: "POST",
333
+ body: JSON.stringify(request)
334
+ });
335
+ }
336
+ /**
337
+ * Multipart variant of `submitRun` for runs that carry transient
338
+ * (per-run) skill bundles and/or transient AgentsMd content.
339
+ *
340
+ * The JSON submission travels as the `submission` part; each
341
+ * `InlineSkillRef.slot` in `request.submission.skills` MUST be
342
+ * mirrored by exactly one `skill:<slot>` part with the bundle bytes.
343
+ * Each `InlineAgentsMdRef.slot` in `request.submission.agentsMd`
344
+ * MUST be mirrored by exactly one `agentsmd:<slot>` part with the
345
+ * markdown text.
346
+ *
347
+ * The BFF re-canonicalises and re-hashes each bundle/file; a
348
+ * `contentHash` mismatch is rejected with a deterministic error.
349
+ *
350
+ * At least one of `bundles` or `agentsMdParts` must be non-empty.
351
+ */
352
+ export async function submitRunMultipart(http, request, bundles, agentsMdParts, fileParts) {
353
+ const hasBundles = Array.isArray(bundles) && bundles.length > 0;
354
+ const hasAgentsMd = Array.isArray(agentsMdParts) && agentsMdParts.length > 0;
355
+ const hasFiles = Array.isArray(fileParts) && fileParts.length > 0;
356
+ if (!hasBundles && !hasAgentsMd && !hasFiles) {
357
+ throw new Error("submitRunMultipart: bundles, agentsMdParts, or fileParts must be non-empty");
358
+ }
359
+ const form = new FormData();
360
+ // Submission rides as a typed JSON Blob so the BFF reads
361
+ // `multipart["submission"]` with the right content-type and never
362
+ // has to re-detect the body shape.
363
+ form.append("submission", new Blob([JSON.stringify(request)], { type: "application/json" }), "submission.json");
364
+ const seen = new Set();
365
+ for (const bundle of bundles) {
366
+ if (typeof bundle.slot !== "string" || !bundle.slot) {
367
+ throw new Error("submitRunMultipart: each bundle must have a non-empty slot id");
368
+ }
369
+ if (seen.has(bundle.slot)) {
370
+ throw new Error(`submitRunMultipart: duplicate inline skill slot "${bundle.slot}"`);
371
+ }
372
+ seen.add(bundle.slot);
373
+ const blob = toBlob(bundle.bytes, "application/zip");
374
+ form.append(`skill:${bundle.slot}`, blob, bundle.filename);
375
+ }
376
+ for (const part of agentsMdParts ?? []) {
377
+ if (typeof part.slot !== "string" || !part.slot) {
378
+ throw new Error("submitRunMultipart: each agentsMd part must have a non-empty slot id");
379
+ }
380
+ const partKey = `agentsmd:${part.slot}`;
381
+ if (seen.has(partKey)) {
382
+ throw new Error(`submitRunMultipart: duplicate agentsMd slot "${part.slot}"`);
383
+ }
384
+ seen.add(partKey);
385
+ const blob = new Blob([part.content], { type: "text/plain" });
386
+ form.append(partKey, blob, part.filename);
387
+ }
388
+ for (const part of fileParts ?? []) {
389
+ if (typeof part.slot !== "string" || !part.slot) {
390
+ throw new Error("submitRunMultipart: each file part must have a non-empty slot id");
391
+ }
392
+ const partKey = `file:${part.slot}`;
393
+ if (seen.has(partKey)) {
394
+ throw new Error(`submitRunMultipart: duplicate file slot "${part.slot}"`);
395
+ }
396
+ seen.add(partKey);
397
+ const blob = toBlob(part.bytes, "application/zip");
398
+ form.append(partKey, blob, part.filename);
399
+ }
400
+ return http.request("/api/runs", {
401
+ method: "POST",
402
+ body: form
403
+ });
404
+ }
405
+ /**
406
+ * Upload a workspace skill bundle as a zip blob. The hosted API runs
407
+ * the two-phase flow internally (insert pending row, stream bytes into
408
+ * object storage, validate manifest, transition to ready) and returns
409
+ * the finalized `Skill`. Use `Skill.fromPath` / `Skill.upload` in the
410
+ * SDK to build the body; this transport function only knows about
411
+ * bytes.
412
+ */
413
+ export async function createSkillBundle(http, args) {
414
+ const form = new FormData();
415
+ form.append("name", args.name);
416
+ const blobBody = toBlob(args.body, args.contentType ?? "application/zip");
417
+ form.append("bundle", blobBody, args.filename ?? `${args.name}.zip`);
418
+ const result = await http.request("/api/skills", {
419
+ method: "POST",
420
+ body: form
421
+ });
422
+ return unwrapSkill(result);
423
+ }
424
+ /**
425
+ * Upload a workspace skill bundle DIRECTLY to object storage via the presign
426
+ * flow, so the bytes never transit the hosted API (bundle size bounded by the
427
+ * object store, not API memory). Falls back to the buffered multipart
428
+ * `createSkillBundle` when the hosted API has no object-store upload
429
+ * credentials (503 `presign_unconfigured`).
430
+ *
431
+ * 1. POST /api/skills/presign { name, hash, sizeBytes } → { uploadUrl, requiredHeaders, skillId }
432
+ * 2. PUT bytes → uploadUrl (signed checksum; the store rejects a mismatch)
433
+ * 3. POST /api/skills/:id/finalize { manifest } → finalized Skill
434
+ *
435
+ * `manifest` is the client-computed bundle manifest (the caller already
436
+ * validated the zip shape before hashing); the Worker records it on finalize
437
+ * without re-buffering the object.
438
+ */
439
+ export async function createSkillBundleDirect(http, fetchImpl, args) {
440
+ let presign;
441
+ try {
442
+ presign = await http.request("/api/skills/presign", {
443
+ method: "POST",
444
+ headers: { "content-type": "application/json" },
445
+ body: JSON.stringify({ name: args.name, hash: args.contentHash, sizeBytes: args.body.byteLength })
446
+ });
447
+ }
448
+ catch (err) {
449
+ const status = err.status;
450
+ const code = (err.details ?? {}).code;
451
+ if (status === 503 && code === "presign_unconfigured") {
452
+ return createSkillBundle(http, {
453
+ name: args.name,
454
+ body: args.body,
455
+ ...(args.contentType ? { contentType: args.contentType } : {})
456
+ });
457
+ }
458
+ throw err;
459
+ }
460
+ const putRes = await fetchImpl(presign.uploadUrl, {
461
+ method: "PUT",
462
+ headers: { "content-type": args.contentType ?? "application/zip", ...(presign.requiredHeaders ?? {}) },
463
+ body: args.body
464
+ });
465
+ if (!putRes.ok) {
466
+ const detail = await putRes.text().catch(() => "");
467
+ throw new Error(`createSkillBundleDirect: direct upload PUT failed (status ${putRes.status})${detail ? `: ${detail.slice(0, 500)}` : ""}`);
468
+ }
469
+ const result = await http.request(`/api/skills/${encodeURIComponent(presign.skillId)}/finalize`, {
470
+ method: "POST",
471
+ headers: { "content-type": "application/json" },
472
+ body: JSON.stringify({ manifest: args.manifest })
473
+ });
474
+ return unwrapSkill(result);
475
+ }
476
+ export async function listSkills(http) {
477
+ const result = await http.request("/api/skills");
478
+ if (Array.isArray(result)) {
479
+ return result;
480
+ }
481
+ return result.skills;
482
+ }
483
+ export async function getSkill(http, skillId) {
484
+ const result = await http.request(`/api/skills/${encodeURIComponent(skillId)}`);
485
+ return unwrapSkill(result);
486
+ }
487
+ export async function deleteSkill(http, skillId) {
488
+ await http.request(`/api/skills/${encodeURIComponent(skillId)}`, {
489
+ method: "DELETE"
490
+ });
491
+ }
492
+ /**
493
+ * Lookup a live workspace skill by `(name, contentHash)`. Returns the
494
+ * matching `Skill` record or null when no live row carries that hash.
495
+ *
496
+ * `contentHash` is the wire format `sha256:<hex>` as returned by
497
+ * `hashSkillBundle`. This powers `Skill.uploadIfChanged` — the SDK
498
+ * computes the hash locally and calls this function to skip the upload
499
+ * when the bytes already exist.
500
+ */
501
+ export async function findSkillByHash(http, args) {
502
+ const params = new URLSearchParams({
503
+ name: args.name,
504
+ content_hash: args.contentHash
505
+ });
506
+ const result = await http.request(`/api/skills/by-hash?${params.toString()}`);
507
+ return result.skill ?? null;
508
+ }
509
+ /**
510
+ * Lookup a live workspace skill by `name`. Returns the matching `Skill`
511
+ * record or null when no live row carries that name. Implemented as a
512
+ * list-and-filter on the existing `/api/skills` endpoint — the
513
+ * indexed by-hash route is reserved for `uploadIfChanged`.
514
+ */
515
+ export async function findSkillByName(http, name) {
516
+ const skills = await listSkills(http);
517
+ return skills.find((skill) => skill.name === name) ?? null;
518
+ }
519
+ // ===========================================================================
520
+ // AgentsMd (workspace_files kind='agentsmd') operations
521
+ // ===========================================================================
522
+ /**
523
+ * Upload a workspace AgentsMd file as a markdown string. The BFF
524
+ * canonicalises the content into a deterministic zip with AGENTS.md at
525
+ * root and runs the two-phase pending → ready upload.
526
+ */
527
+ export async function createAgentsMd(http, args) {
528
+ const form = new FormData();
529
+ form.append("name", args.name);
530
+ form.append("content", new Blob([args.content], { type: "text/plain" }), "AGENTS.md");
531
+ const result = await http.request("/api/agentsmd", { method: "POST", body: form });
532
+ return unwrapAgentsMd(result);
533
+ }
534
+ export async function listAgentsMd(http) {
535
+ const result = await http.request("/api/agentsmd");
536
+ if (Array.isArray(result)) {
537
+ return result;
538
+ }
539
+ return result.agentsMd;
540
+ }
541
+ export async function getAgentsMd(http, agentsMdId) {
542
+ const result = await http.request(`/api/agentsmd/${encodeURIComponent(agentsMdId)}`);
543
+ return unwrapAgentsMd(result);
544
+ }
545
+ export async function deleteAgentsMd(http, agentsMdId) {
546
+ await http.request(`/api/agentsmd/${encodeURIComponent(agentsMdId)}`, {
547
+ method: "DELETE"
548
+ });
549
+ }
550
+ function unwrapAgentsMd(result) {
551
+ if (result && typeof result === "object" && "agentsMd" in result) {
552
+ return result.agentsMd;
553
+ }
554
+ return result;
555
+ }
556
+ // ===========================================================================
557
+ // File (workspace_files kind='file') operations
558
+ // ===========================================================================
559
+ /**
560
+ * Upload a workspace File as a zip bundle. The BFF canonicalises the
561
+ * content and runs the two-phase pending → ready upload.
562
+ */
563
+ export async function createFile(http, args) {
564
+ const form = new FormData();
565
+ form.append("name", args.name);
566
+ const blob = toBlob(args.bytes, "application/zip");
567
+ form.append("bundle", blob, `${args.name}.zip`);
568
+ const result = await http.request("/api/files", { method: "POST", body: form });
569
+ return unwrapFile(result);
570
+ }
571
+ export async function listFiles(http) {
572
+ const result = await http.request("/api/files");
573
+ if (Array.isArray(result)) {
574
+ return result;
575
+ }
576
+ return result.files;
577
+ }
578
+ export async function getFile(http, fileId) {
579
+ const result = await http.request(`/api/files/${encodeURIComponent(fileId)}`);
580
+ return unwrapFile(result);
581
+ }
582
+ export async function deleteFile(http, fileId) {
583
+ await http.request(`/api/files/${encodeURIComponent(fileId)}`, {
584
+ method: "DELETE"
585
+ });
586
+ }
587
+ function unwrapFile(result) {
588
+ if (result && typeof result === "object" && "file" in result) {
589
+ return result.file;
590
+ }
591
+ return result;
592
+ }
593
+ function unwrapSkill(result) {
594
+ if (result && typeof result === "object" && "skill" in result) {
595
+ return result.skill;
596
+ }
597
+ return result;
598
+ }
599
+ function toBlob(input, contentType) {
600
+ if (input instanceof Blob) {
601
+ return input;
602
+ }
603
+ if (input instanceof Uint8Array) {
604
+ // BlobPart accepts ArrayBufferView, but lib.dom's overload set
605
+ // narrows on the underlying buffer kind. Slice into a fresh
606
+ // ArrayBuffer so a SharedArrayBuffer-backed Uint8Array works.
607
+ const copy = new Uint8Array(input.byteLength);
608
+ copy.set(input);
609
+ return new Blob([copy.buffer], { type: contentType });
610
+ }
611
+ return new Blob([input], { type: contentType });
612
+ }
613
+ function hasRun(value) {
614
+ return Boolean(value && typeof value === "object" && "run" in value);
615
+ }
616
+ /**
617
+ * Upload bytes to the hosted API's content-addressable asset endpoint.
618
+ * Returns a storage-neutral asset id suitable for `kind:"asset"` refs in a
619
+ * later run submission.
620
+ */
621
+ export async function uploadWorkspaceAsset(http, input) {
622
+ return http.request("/assets", {
623
+ method: "POST",
624
+ headers: {
625
+ "content-type": input.contentType ?? "application/octet-stream",
626
+ "content-length": String(input.bytes.byteLength),
627
+ ...(input.contentHash ? { "x-asset-hash": input.contentHash } : {})
628
+ },
629
+ body: input.bytes
630
+ });
631
+ }
632
+ //# sourceMappingURL=operations.js.map