antpath 0.9.2 → 0.10.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -528,16 +528,26 @@ function extractErrorMessage(body) {
528
528
  var operations_exports = {};
529
529
  __export(operations_exports, {
530
530
  cancelRun: () => cancelRun,
531
+ createAgentsMd: () => createAgentsMd,
532
+ createFile: () => createFile,
531
533
  createOutputLink: () => createOutputLink,
532
534
  createSkillBundle: () => createSkillBundle,
535
+ deleteAgentsMd: () => deleteAgentsMd,
536
+ deleteFile: () => deleteFile,
533
537
  deleteRun: () => deleteRun,
534
538
  deleteSkill: () => deleteSkill,
535
539
  downloadRunArchive: () => downloadRunArchive,
540
+ finalizeAssetUpload: () => finalizeAssetUpload,
536
541
  findSkillByHash: () => findSkillByHash,
537
542
  findSkillByName: () => findSkillByName,
543
+ getAgentsMd: () => getAgentsMd,
544
+ getFile: () => getFile,
538
545
  getRun: () => getRun,
539
546
  getRunUnit: () => getRunUnit,
540
547
  getSkill: () => getSkill,
548
+ initAssetUpload: () => initAssetUpload,
549
+ listAgentsMd: () => listAgentsMd,
550
+ listFiles: () => listFiles,
541
551
  listOutputs: () => listOutputs,
542
552
  listRunEvents: () => listRunEvents,
543
553
  listSkills: () => listSkills,
@@ -589,9 +599,12 @@ async function submitRunFlat(http, request) {
589
599
  body: JSON.stringify(request)
590
600
  });
591
601
  }
592
- async function submitRunFlatMultipart(http, request, bundles) {
593
- if (!Array.isArray(bundles) || bundles.length === 0) {
594
- throw new Error("submitRunFlatMultipart: bundles must be a non-empty array");
602
+ async function submitRunFlatMultipart(http, request, bundles, agentsMdParts, fileParts) {
603
+ const hasBundles = Array.isArray(bundles) && bundles.length > 0;
604
+ const hasAgentsMd = Array.isArray(agentsMdParts) && agentsMdParts.length > 0;
605
+ const hasFiles = Array.isArray(fileParts) && fileParts.length > 0;
606
+ if (!hasBundles && !hasAgentsMd && !hasFiles) {
607
+ throw new Error("submitRunFlatMultipart: bundles, agentsMdParts, or fileParts must be non-empty");
595
608
  }
596
609
  const form = new FormData();
597
610
  form.append("submission", new Blob([JSON.stringify(request)], { type: "application/json" }), "submission.json");
@@ -607,6 +620,30 @@ async function submitRunFlatMultipart(http, request, bundles) {
607
620
  const blob = toBlob(bundle.bytes, "application/zip");
608
621
  form.append(`skill:${bundle.slot}`, blob, bundle.filename);
609
622
  }
623
+ for (const part of agentsMdParts ?? []) {
624
+ if (typeof part.slot !== "string" || !part.slot) {
625
+ throw new Error("submitRunFlatMultipart: each agentsMd part must have a non-empty slot id");
626
+ }
627
+ const partKey = `agentsmd:${part.slot}`;
628
+ if (seen.has(partKey)) {
629
+ throw new Error(`submitRunFlatMultipart: duplicate agentsMd slot "${part.slot}"`);
630
+ }
631
+ seen.add(partKey);
632
+ const blob = new Blob([part.content], { type: "text/plain" });
633
+ form.append(partKey, blob, part.filename);
634
+ }
635
+ for (const part of fileParts ?? []) {
636
+ if (typeof part.slot !== "string" || !part.slot) {
637
+ throw new Error("submitRunFlatMultipart: each file part must have a non-empty slot id");
638
+ }
639
+ const partKey = `file:${part.slot}`;
640
+ if (seen.has(partKey)) {
641
+ throw new Error(`submitRunFlatMultipart: duplicate file slot "${part.slot}"`);
642
+ }
643
+ seen.add(partKey);
644
+ const blob = toBlob(part.bytes, "application/zip");
645
+ form.append(partKey, blob, part.filename);
646
+ }
610
647
  return http.request("/api/runs", {
611
648
  method: "POST",
612
649
  body: form
@@ -651,6 +688,65 @@ async function findSkillByName(http, name) {
651
688
  const skills = await listSkills(http);
652
689
  return skills.find((skill) => skill.name === name) ?? null;
653
690
  }
691
+ async function createAgentsMd(http, args) {
692
+ const form = new FormData();
693
+ form.append("name", args.name);
694
+ form.append("content", new Blob([args.content], { type: "text/plain" }), "AGENTS.md");
695
+ const result = await http.request("/api/agentsmd", { method: "POST", body: form });
696
+ return unwrapAgentsMd(result);
697
+ }
698
+ async function listAgentsMd(http) {
699
+ const result = await http.request("/api/agentsmd");
700
+ if (Array.isArray(result)) {
701
+ return result;
702
+ }
703
+ return result.agentsMd;
704
+ }
705
+ async function getAgentsMd(http, agentsMdId) {
706
+ const result = await http.request(`/api/agentsmd/${encodeURIComponent(agentsMdId)}`);
707
+ return unwrapAgentsMd(result);
708
+ }
709
+ async function deleteAgentsMd(http, agentsMdId) {
710
+ await http.request(`/api/agentsmd/${encodeURIComponent(agentsMdId)}`, {
711
+ method: "DELETE"
712
+ });
713
+ }
714
+ function unwrapAgentsMd(result) {
715
+ if (result && typeof result === "object" && "agentsMd" in result) {
716
+ return result.agentsMd;
717
+ }
718
+ return result;
719
+ }
720
+ async function createFile(http, args) {
721
+ const form = new FormData();
722
+ form.append("name", args.name);
723
+ const blob = toBlob(args.bytes, "application/zip");
724
+ form.append("bundle", blob, `${args.name}.zip`);
725
+ const result = await http.request("/api/files", { method: "POST", body: form });
726
+ return unwrapFile(result);
727
+ }
728
+ async function listFiles(http) {
729
+ const result = await http.request("/api/files");
730
+ if (Array.isArray(result)) {
731
+ return result;
732
+ }
733
+ return result.files;
734
+ }
735
+ async function getFile(http, fileId) {
736
+ const result = await http.request(`/api/files/${encodeURIComponent(fileId)}`);
737
+ return unwrapFile(result);
738
+ }
739
+ async function deleteFile(http, fileId) {
740
+ await http.request(`/api/files/${encodeURIComponent(fileId)}`, {
741
+ method: "DELETE"
742
+ });
743
+ }
744
+ function unwrapFile(result) {
745
+ if (result && typeof result === "object" && "file" in result) {
746
+ return result.file;
747
+ }
748
+ return result;
749
+ }
654
750
  function unwrapSkill(result) {
655
751
  if (result && typeof result === "object" && "skill" in result) {
656
752
  return result.skill;
@@ -671,6 +767,18 @@ function toBlob(input, contentType) {
671
767
  function hasRun(value) {
672
768
  return Boolean(value && typeof value === "object" && "run" in value);
673
769
  }
770
+ async function initAssetUpload(http, input) {
771
+ return http.request("/api/assets/upload-init", {
772
+ method: "POST",
773
+ body: JSON.stringify(input)
774
+ });
775
+ }
776
+ async function finalizeAssetUpload(http, input) {
777
+ await http.request("/api/assets/finalize", {
778
+ method: "POST",
779
+ body: JSON.stringify(input)
780
+ });
781
+ }
674
782
 
675
783
  // ../shared/dist/proxy-validation.js
676
784
  function validateProxyAuth(endpoints, auth) {
@@ -1424,6 +1532,8 @@ async function runRunCmd(io2, argv) {
1424
1532
  ...blueprint.system ? { system: blueprint.system } : {},
1425
1533
  prompt: promptArray,
1426
1534
  skills,
1535
+ agentsMd: [],
1536
+ files: [],
1427
1537
  mcpServers: mcpServersForSubmission,
1428
1538
  ...blueprint.environment ? { environment: blueprint.environment } : {},
1429
1539
  ...blueprint.metadata ? { metadata: blueprint.metadata } : {}
@@ -2524,7 +2634,7 @@ async function zipDirectory(rootDir) {
2524
2634
  throw new Error(`${rootDir} contains no regular files`);
2525
2635
  }
2526
2636
  if (!hasSkillMd) {
2527
- throw new Error('skill bundle must contain a "SKILL.md" file at the root');
2637
+ throw new Error('skill bundle must contain a "SKILL.md" file at the root. For AGENTS.md / generic files use the corresponding `antpath agentsmd` / `antpath files` commands instead.');
2528
2638
  }
2529
2639
  const sorted = [...collected.entries()].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
2530
2640
  const zippable = {};
@@ -1 +1 @@
1
- f176fe0055e6734c13959869bba99513459b1caf7f982a7088e638a69bbdd528 cli.mjs
1
+ be4a58f5a53548888b5e515cbb87dec475e869b6b1a43122c0b74ef19b9922cb cli.mjs
package/dist/client.d.ts CHANGED
@@ -1,8 +1,11 @@
1
- import { HttpClient, type FetchLike, type Output, type PlatformFlatRunSubmissionInput, type PlatformFlatSubmission, type PlatformInlineSecrets, type PlatformProxyEndpoint, type PlatformProxyEndpointAuth, type Run, type RunEvent, type RunUnit, type SignedOutputLink, type Skill as SkillRecord, type WhoAmI } from "./_shared/index.js";
1
+ import { HttpClient, type AgentsMdRecord, type FetchLike, type FileRecord, type Output, type PlatformFlatRunSubmissionInput, type PlatformFlatSubmission, type PlatformInlineSecrets, type PlatformProxyEndpoint, type PlatformProxyEndpointAuth, type Run, type RunEvent, type RunUnit, type SignedOutputLink, type Skill as SkillRecord, type WhoAmI } from "./_shared/index.js";
2
2
  import type { Blueprint } from "./blueprint.js";
3
+ import { AgentsMd } from "./agents-md.js";
4
+ import { File } from "./file.js";
3
5
  import { McpServer } from "./mcp-server.js";
4
6
  import { ProxyEndpoint } from "./proxy-endpoint.js";
5
7
  import { Skill } from "./skill.js";
8
+ import type { ChunkedUploadClient } from "./skill.js";
6
9
  export interface AntpathClientOptions {
7
10
  /** Workspace-scoped SDK API token. */
8
11
  readonly apiToken: string;
@@ -46,6 +49,8 @@ export interface SubmitRunOptions {
46
49
  readonly system?: string;
47
50
  readonly prompt: string | readonly string[];
48
51
  readonly skills?: readonly Skill[];
52
+ readonly agentsMd?: readonly AgentsMd[];
53
+ readonly files?: readonly File[];
49
54
  readonly mcpServers?: readonly McpServer[];
50
55
  readonly environment?: PlatformFlatSubmission["environment"];
51
56
  readonly metadata?: PlatformFlatSubmission["metadata"];
@@ -172,6 +177,62 @@ export declare class SkillsClient {
172
177
  readonly body: Uint8Array;
173
178
  }): Promise<SkillRecord>;
174
179
  }
180
+ /**
181
+ * Workspace AgentsMd admin operations exposed under `client.agentsMd`.
182
+ *
183
+ * Persisting a new AgentsMd goes through
184
+ * `AgentsMd.fromContent("…", { name }).upload(client)` or
185
+ * `AgentsMd.fromPath('./AGENTS.md').upload(client)` — there is no
186
+ * `client.agentsMd.upload` or `client.agentsMd.fromPath` verb. The class
187
+ * is the single creation surface; the client is the transport.
188
+ *
189
+ * The internal `_uploadAgentsMd(args)` entry point is consumed by
190
+ * `AgentsMd.upload(client)` and is not part of the public API — the
191
+ * leading underscore is a "do not call from user code" marker.
192
+ */
193
+ export declare class AgentsMdClient {
194
+ #private;
195
+ constructor(http: HttpClient);
196
+ list(): Promise<readonly AgentsMdRecord[]>;
197
+ get(agentsMdId: string): Promise<AgentsMdRecord>;
198
+ delete(agentsMdId: string): Promise<void>;
199
+ /**
200
+ * Internal: post an AgentsMd markdown string to the BFF. Only
201
+ * `AgentsMd.upload` calls this. NOT part of the public API.
202
+ */
203
+ _uploadAgentsMd(args: {
204
+ readonly name: string;
205
+ readonly content: string;
206
+ }): Promise<AgentsMdRecord>;
207
+ }
208
+ /**
209
+ * Workspace File admin operations exposed under `client.files`.
210
+ *
211
+ * Persisting a new File goes through
212
+ * `File.fromPath('./data/').upload(client)` or
213
+ * `File.fromBytes({ name, bytes }).upload(client)` — there is no
214
+ * `client.files.upload` verb. The class is the single creation surface;
215
+ * the client is the transport.
216
+ *
217
+ * The internal `_uploadFile(args)` entry point is consumed by
218
+ * `File.upload(client)` and is not part of the public API — the
219
+ * leading underscore is a "do not call from user code" marker.
220
+ */
221
+ export declare class FilesClient {
222
+ #private;
223
+ constructor(http: HttpClient);
224
+ list(): Promise<readonly FileRecord[]>;
225
+ get(fileId: string): Promise<FileRecord>;
226
+ delete(fileId: string): Promise<void>;
227
+ /**
228
+ * Internal: post a pre-bundled file zip to the BFF. Only
229
+ * `File.upload` calls this. NOT part of the public API.
230
+ */
231
+ _uploadFile(args: {
232
+ readonly name: string;
233
+ readonly bytes: Uint8Array;
234
+ }): Promise<FileRecord>;
235
+ }
175
236
  /**
176
237
  * Unified user-facing client for the antpath platform. The same class
177
238
  * powers the published `antpath` SDK and (under the hood) every host-side
@@ -186,6 +247,8 @@ export declare class SkillsClient {
186
247
  export declare class AntpathClient {
187
248
  #private;
188
249
  readonly skills: SkillsClient;
250
+ readonly agentsMd: AgentsMdClient;
251
+ readonly files: FilesClient;
189
252
  constructor(options: AntpathClientOptions);
190
253
  /**
191
254
  * Internal: a `Skill.upload(this)` shortcut that bypasses
@@ -196,6 +259,32 @@ export declare class AntpathClient {
196
259
  readonly name: string;
197
260
  readonly body: Uint8Array;
198
261
  }): Promise<SkillRecord>;
262
+ /**
263
+ * Internal: an `AgentsMd.upload(this)` shortcut that bypasses
264
+ * `client.agentsMd` indirection. Forwarded to
265
+ * `AgentsMdClient._uploadAgentsMd`. NOT part of the public API.
266
+ */
267
+ _uploadAgentsMd(args: {
268
+ readonly name: string;
269
+ readonly content: string;
270
+ }): Promise<AgentsMdRecord>;
271
+ /**
272
+ * Internal: a `File.upload(this)` shortcut that bypasses
273
+ * `client.files` indirection. Forwarded to
274
+ * `FilesClient._uploadFile`. NOT part of the public API.
275
+ */
276
+ _uploadFile(args: {
277
+ readonly name: string;
278
+ readonly bytes: Uint8Array;
279
+ }): Promise<FileRecord>;
280
+ /**
281
+ * Internal: chunked-upload entry point for `Skill.upload` and
282
+ * `File.upload` when the bundle exceeds the 6 MiB threshold.
283
+ * Drives the three-step TUS flow: init → uploadChunked → finalize.
284
+ * NOT part of the public API — the underscore is a "do not call
285
+ * from user code" marker.
286
+ */
287
+ readonly _chunkedUpload: ChunkedUploadClient;
199
288
  /**
200
289
  * Submit a run and wait for it to reach a terminal state. Returns the
201
290
  * final `Run` record. For long-running flows, prefer `submitRun` +
@@ -212,10 +301,14 @@ export declare class AntpathClient {
212
301
  * the run snapshot.
213
302
  *
214
303
  * Unstaged transient skills (`Skill.fromFiles` / `Skill.fromPath`
215
- * without a prior `.upload`) are not yet accepted by the dashboard
216
- * BFF passing one to `submitRun` throws with a directive error
217
- * pointing to `await skill.upload(client)`. The workspace-skill path
218
- * works end-to-end today and is the canonical Phase 0 surface.
304
+ * without a prior `.upload`) are accepted: the SDK switches to a
305
+ * multipart body that carries the canonical zip bytes alongside the
306
+ * JSON submission. The dashboard BFF ingests each one through the
307
+ * standard workspace-skill upload pipeline (dedup by content hash;
308
+ * upload via the existing two-phase pending → ready flow) and
309
+ * rewrites the run's `skills[]` to reference the resulting `skl_*`
310
+ * ids. The bytes persist on antpath; the user can browse and
311
+ * download the resulting workspace skill from the dashboard.
219
312
  */
220
313
  submitRun(options: SubmitRunOptions): Promise<RunRef>;
221
314
  getRun(runId: string): Promise<Run>;
package/dist/client.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { HttpClient, operations } from "./_shared/index.js";
2
+ import { AgentsMd } from "./agents-md.js";
3
+ import { File } from "./file.js";
2
4
  import { McpServer } from "./mcp-server.js";
3
5
  import { splitProxyEndpoints } from "./proxy-endpoint.js";
4
6
  import { Skill } from "./skill.js";
@@ -133,6 +135,76 @@ export class SkillsClient {
133
135
  });
134
136
  }
135
137
  }
138
+ /**
139
+ * Workspace AgentsMd admin operations exposed under `client.agentsMd`.
140
+ *
141
+ * Persisting a new AgentsMd goes through
142
+ * `AgentsMd.fromContent("…", { name }).upload(client)` or
143
+ * `AgentsMd.fromPath('./AGENTS.md').upload(client)` — there is no
144
+ * `client.agentsMd.upload` or `client.agentsMd.fromPath` verb. The class
145
+ * is the single creation surface; the client is the transport.
146
+ *
147
+ * The internal `_uploadAgentsMd(args)` entry point is consumed by
148
+ * `AgentsMd.upload(client)` and is not part of the public API — the
149
+ * leading underscore is a "do not call from user code" marker.
150
+ */
151
+ export class AgentsMdClient {
152
+ #http;
153
+ constructor(http) {
154
+ this.#http = http;
155
+ }
156
+ list() {
157
+ return operations.listAgentsMd(this.#http);
158
+ }
159
+ get(agentsMdId) {
160
+ return operations.getAgentsMd(this.#http, agentsMdId);
161
+ }
162
+ delete(agentsMdId) {
163
+ return operations.deleteAgentsMd(this.#http, agentsMdId);
164
+ }
165
+ /**
166
+ * Internal: post an AgentsMd markdown string to the BFF. Only
167
+ * `AgentsMd.upload` calls this. NOT part of the public API.
168
+ */
169
+ async _uploadAgentsMd(args) {
170
+ return operations.createAgentsMd(this.#http, args);
171
+ }
172
+ }
173
+ /**
174
+ * Workspace File admin operations exposed under `client.files`.
175
+ *
176
+ * Persisting a new File goes through
177
+ * `File.fromPath('./data/').upload(client)` or
178
+ * `File.fromBytes({ name, bytes }).upload(client)` — there is no
179
+ * `client.files.upload` verb. The class is the single creation surface;
180
+ * the client is the transport.
181
+ *
182
+ * The internal `_uploadFile(args)` entry point is consumed by
183
+ * `File.upload(client)` and is not part of the public API — the
184
+ * leading underscore is a "do not call from user code" marker.
185
+ */
186
+ export class FilesClient {
187
+ #http;
188
+ constructor(http) {
189
+ this.#http = http;
190
+ }
191
+ list() {
192
+ return operations.listFiles(this.#http);
193
+ }
194
+ get(fileId) {
195
+ return operations.getFile(this.#http, fileId);
196
+ }
197
+ delete(fileId) {
198
+ return operations.deleteFile(this.#http, fileId);
199
+ }
200
+ /**
201
+ * Internal: post a pre-bundled file zip to the BFF. Only
202
+ * `File.upload` calls this. NOT part of the public API.
203
+ */
204
+ async _uploadFile(args) {
205
+ return operations.createFile(this.#http, args);
206
+ }
207
+ }
136
208
  /**
137
209
  * Unified user-facing client for the antpath platform. The same class
138
210
  * powers the published `antpath` SDK and (under the hood) every host-side
@@ -147,6 +219,8 @@ export class SkillsClient {
147
219
  export class AntpathClient {
148
220
  #http;
149
221
  skills;
222
+ agentsMd;
223
+ files;
150
224
  constructor(options) {
151
225
  if (!options.apiToken) {
152
226
  throw new Error("AntpathClient: apiToken is required");
@@ -157,6 +231,8 @@ export class AntpathClient {
157
231
  ...(options.fetch ? { fetch: options.fetch } : {})
158
232
  });
159
233
  this.skills = new SkillsClient(this.#http);
234
+ this.agentsMd = new AgentsMdClient(this.#http);
235
+ this.files = new FilesClient(this.#http);
160
236
  }
161
237
  /**
162
238
  * Internal: a `Skill.upload(this)` shortcut that bypasses
@@ -166,6 +242,33 @@ export class AntpathClient {
166
242
  async _uploadSkillBundle(args) {
167
243
  return this.skills._uploadSkillBundle(args);
168
244
  }
245
+ /**
246
+ * Internal: an `AgentsMd.upload(this)` shortcut that bypasses
247
+ * `client.agentsMd` indirection. Forwarded to
248
+ * `AgentsMdClient._uploadAgentsMd`. NOT part of the public API.
249
+ */
250
+ async _uploadAgentsMd(args) {
251
+ return this.agentsMd._uploadAgentsMd(args);
252
+ }
253
+ /**
254
+ * Internal: a `File.upload(this)` shortcut that bypasses
255
+ * `client.files` indirection. Forwarded to
256
+ * `FilesClient._uploadFile`. NOT part of the public API.
257
+ */
258
+ async _uploadFile(args) {
259
+ return this.files._uploadFile(args);
260
+ }
261
+ /**
262
+ * Internal: chunked-upload entry point for `Skill.upload` and
263
+ * `File.upload` when the bundle exceeds the 6 MiB threshold.
264
+ * Drives the three-step TUS flow: init → uploadChunked → finalize.
265
+ * NOT part of the public API — the underscore is a "do not call
266
+ * from user code" marker.
267
+ */
268
+ _chunkedUpload = {
269
+ init: (args) => operations.initAssetUpload(this.#http, args),
270
+ finalize: (args) => operations.finalizeAssetUpload(this.#http, args)
271
+ };
169
272
  /**
170
273
  * Submit a run and wait for it to reach a terminal state. Returns the
171
274
  * final `Run` record. For long-running flows, prefer `submitRun` +
@@ -185,10 +288,14 @@ export class AntpathClient {
185
288
  * the run snapshot.
186
289
  *
187
290
  * Unstaged transient skills (`Skill.fromFiles` / `Skill.fromPath`
188
- * without a prior `.upload`) are not yet accepted by the dashboard
189
- * BFF passing one to `submitRun` throws with a directive error
190
- * pointing to `await skill.upload(client)`. The workspace-skill path
191
- * works end-to-end today and is the canonical Phase 0 surface.
291
+ * without a prior `.upload`) are accepted: the SDK switches to a
292
+ * multipart body that carries the canonical zip bytes alongside the
293
+ * JSON submission. The dashboard BFF ingests each one through the
294
+ * standard workspace-skill upload pipeline (dedup by content hash;
295
+ * upload via the existing two-phase pending → ready flow) and
296
+ * rewrites the run's `skills[]` to reference the resulting `skl_*`
297
+ * ids. The bytes persist on antpath; the user can browse and
298
+ * download the resulting workspace skill from the dashboard.
192
299
  */
193
300
  async submitRun(options) {
194
301
  if (!options || typeof options !== "object") {
@@ -204,25 +311,16 @@ export class AntpathClient {
204
311
  const { endpoints: proxyEndpointDeclarations, auth: proxyEndpointAuthFromInstances } = splitProxyEndpoints(options.proxyEndpoints ?? []);
205
312
  const mergedProxyAuth = mergeProxyEndpointAuth(proxyEndpointAuthFromInstances, options.secrets.proxyEndpointAuth ?? []);
206
313
  const { skillRefs, transientBundles } = prepareSkills(options.skills ?? []);
207
- if (transientBundles.length > 0) {
208
- // The dashboard BFF's multipart submitRun path is not yet wired up
209
- // (it returns HTTP 501). Short-circuit here with a directive
210
- // message so users do not pay a doomed network round-trip and
211
- // mistake an unimplemented feature for a regression. The
212
- // workaround — persist the skill once, reference it on the run —
213
- // works today and is the canonical Phase 0 path.
214
- const names = transientBundles.map((b) => `\`${b.slot}\``).join(", ");
215
- throw new Error(`AntpathClient.submitRun: unstaged transient skills (${names}) are not yet accepted ` +
216
- `by the dashboard BFF (HTTP 501 multipart submitRun). Persist the skill first with ` +
217
- `\`const persisted = await skill.upload(client)\` and pass \`persisted\` (or ` +
218
- `\`Skill.fromId(persisted.record.id)\`) on the run instead.`);
219
- }
314
+ const { agentsMdRefs, transientAgentsMdParts } = prepareAgentsMd(options.agentsMd ?? []);
315
+ const { fileRefs, transientFileParts } = prepareFiles(options.files ?? []);
220
316
  const { submissionMcpServers, mergedMcpSecrets } = mergeMcpServers(options.mcpServers ?? [], options.secrets.mcpServers ?? []);
221
317
  const submission = {
222
318
  model: options.model,
223
319
  ...(options.system ? { system: options.system } : {}),
224
320
  prompt,
225
321
  skills: skillRefs,
322
+ agentsMd: agentsMdRefs,
323
+ files: fileRefs,
226
324
  mcpServers: submissionMcpServers,
227
325
  ...(options.environment ? { environment: options.environment } : {}),
228
326
  ...(options.metadata ? { metadata: options.metadata } : {}),
@@ -244,8 +342,9 @@ export class AntpathClient {
244
342
  ? { proxyEndpoints: proxyEndpointDeclarations }
245
343
  : {})
246
344
  };
247
- const run = transientBundles.length > 0
248
- ? await operations.submitRunFlatMultipart(this.#http, request, transientBundles)
345
+ const isMultipart = transientBundles.length > 0 || transientAgentsMdParts.length > 0 || transientFileParts.length > 0;
346
+ const run = isMultipart
347
+ ? await operations.submitRunFlatMultipart(this.#http, request, transientBundles, transientAgentsMdParts, transientFileParts)
249
348
  : await operations.submitRunFlat(this.#http, request);
250
349
  return new RunRef(this, run.id);
251
350
  }
@@ -441,6 +540,95 @@ function prepareSkills(skills) {
441
540
  }
442
541
  return { skillRefs, transientBundles };
443
542
  }
543
+ /**
544
+ * Walk the user-provided `AgentsMd[]`, validating each instance and
545
+ * producing:
546
+ * - `agentsMdRefs[]` — the wire entries for `submission.agentsMd[]`,
547
+ * with transient refs assigned positional slot ids
548
+ * (`agentsmd-0`, `agentsmd-1`, …).
549
+ * - `transientAgentsMdParts[]` — the content for each transient
550
+ * AgentsMd, parallel-indexed by slot.
551
+ */
552
+ function prepareAgentsMd(agentsMds) {
553
+ const agentsMdRefs = [];
554
+ const transientAgentsMdParts = [];
555
+ let transientIndex = 0;
556
+ for (let i = 0; i < agentsMds.length; i++) {
557
+ const entry = agentsMds[i];
558
+ if (!(entry instanceof AgentsMd)) {
559
+ throw new Error(`AntpathClient.submitRun: agentsMd[${i}] must be an AgentsMd instance`);
560
+ }
561
+ if (entry.isConsumed) {
562
+ throw new Error(`AntpathClient.submitRun: agentsMd[${i}] was already uploaded via agentsMd.upload(client); ` +
563
+ `use the returned AgentsMd or AgentsMd.fromId(record.id) for subsequent runs`);
564
+ }
565
+ if (entry.isUnstaged) {
566
+ const unstaged = entry._takeUnstagedContent();
567
+ if (!unstaged) {
568
+ throw new Error(`AntpathClient.submitRun: agentsMd[${i}] is unstaged but has no content (internal invariant violated)`);
569
+ }
570
+ const slot = `agentsmd-${transientIndex++}`;
571
+ agentsMdRefs.push({
572
+ kind: "transient_agentsmd",
573
+ slot,
574
+ name: unstaged.name,
575
+ contentHash: unstaged.contentHash
576
+ });
577
+ transientAgentsMdParts.push({
578
+ slot,
579
+ content: unstaged.content,
580
+ filename: `${unstaged.name}.md`
581
+ });
582
+ continue;
583
+ }
584
+ agentsMdRefs.push(entry.ref);
585
+ }
586
+ return { agentsMdRefs, transientAgentsMdParts };
587
+ }
588
+ /**
589
+ * Walk the user-provided `File[]`, validating each instance and producing:
590
+ * - `fileRefs[]` — the wire entries for `submission.files[]`, with
591
+ * transient refs assigned positional slot ids (`file-0`, `file-1`, …).
592
+ * - `transientFileParts[]` — the bytes for each transient file, parallel-indexed.
593
+ */
594
+ function prepareFiles(files) {
595
+ const fileRefs = [];
596
+ const transientFileParts = [];
597
+ let transientIndex = 0;
598
+ for (let i = 0; i < files.length; i++) {
599
+ const entry = files[i];
600
+ if (!(entry instanceof File)) {
601
+ throw new Error(`AntpathClient.submitRun: files[${i}] must be a File instance`);
602
+ }
603
+ if (entry.isConsumed) {
604
+ throw new Error(`AntpathClient.submitRun: files[${i}] was already uploaded via file.upload(client); ` +
605
+ `use the returned File or File.fromId(record.id) for subsequent runs`);
606
+ }
607
+ if (entry.isUnstaged) {
608
+ const bundle = entry._takeUnstagedBundle();
609
+ if (!bundle) {
610
+ throw new Error(`AntpathClient.submitRun: files[${i}] is unstaged but has no bytes (internal invariant violated)`);
611
+ }
612
+ const slot = `file-${transientIndex++}`;
613
+ fileRefs.push({
614
+ kind: "transient_file",
615
+ slot,
616
+ name: bundle.name,
617
+ contentHash: bundle.contentHash,
618
+ ...(bundle.mountPath ? { mountPath: bundle.mountPath } : {})
619
+ });
620
+ const shortHash = bundle.contentHash.replace(/^sha256:/, "").slice(0, 12);
621
+ transientFileParts.push({
622
+ slot,
623
+ bytes: bundle.bytes,
624
+ filename: `antpath-${slot}-${shortHash}.zip`
625
+ });
626
+ continue;
627
+ }
628
+ fileRefs.push(entry.ref);
629
+ }
630
+ return { fileRefs, transientFileParts };
631
+ }
444
632
  function mergeMcpServers(inputs, explicitSecrets) {
445
633
  const submissionMcpServers = [];
446
634
  const secretByName = new Map();