antpath 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -14
- package/dist/_shared/blueprint.d.ts +263 -0
- package/dist/_shared/blueprint.js +505 -0
- package/dist/_shared/http.d.ts +6 -1
- package/dist/_shared/http.js +10 -5
- package/dist/_shared/index.d.ts +1 -0
- package/dist/_shared/index.js +1 -0
- package/dist/_shared/operations.d.ts +32 -9
- package/dist/_shared/operations.js +73 -12
- package/dist/_shared/runtime-types.d.ts +30 -0
- package/dist/_shared/stable.d.ts +14 -0
- package/dist/_shared/stable.js +14 -0
- package/dist/_shared/submission.d.ts +55 -0
- package/dist/_shared/submission.js +135 -1
- package/dist/cli.mjs +114 -58
- package/dist/cli.mjs.sha256 +1 -1
- package/dist/client.d.ts +13 -6
- package/dist/client.js +17 -16
- package/dist/client.js.map +1 -1
- package/docs/credentials.md +1 -3
- package/docs/quickstart.md +4 -7
- package/docs/release.md +57 -12
- package/examples/mcp-static-bearer.ts +1 -3
- package/examples/quickstart.ts +1 -3
- package/package.json +2 -3
- package/references/architecture-decisions.md +0 -473
- package/references/implementation-plan.md +0 -452
- package/references/research-sources.md +0 -41
- package/references/testing-strategy.md +0 -29
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Every function takes an HttpClient (so callers control auth + fetch
|
|
7
7
|
* injection) and returns parsed responses.
|
|
8
|
+
*
|
|
9
|
+
* Workspace identity is derived server-side from the API token on
|
|
10
|
+
* every request — callers do not pass `workspaceId`. See
|
|
11
|
+
* `references/development-principles.md` (Agent-first surface design,
|
|
12
|
+
* Concrete rule 3).
|
|
8
13
|
*/
|
|
9
14
|
export async function submitRun(http, request) {
|
|
10
15
|
return http.request("/api/runs", {
|
|
@@ -12,30 +17,86 @@ export async function submitRun(http, request) {
|
|
|
12
17
|
body: JSON.stringify(request)
|
|
13
18
|
});
|
|
14
19
|
}
|
|
15
|
-
export async function getRun(http,
|
|
16
|
-
const result = await http.request(`/api/runs/${encodeURIComponent(runId)}
|
|
20
|
+
export async function getRun(http, runId) {
|
|
21
|
+
const result = await http.request(`/api/runs/${encodeURIComponent(runId)}`);
|
|
17
22
|
return hasRun(result) ? result.run : result;
|
|
18
23
|
}
|
|
19
|
-
export async function listRunEvents(http,
|
|
20
|
-
const result = await http.request(`/api/runs/${encodeURIComponent(runId)}/events
|
|
24
|
+
export async function listRunEvents(http, runId) {
|
|
25
|
+
const result = await http.request(`/api/runs/${encodeURIComponent(runId)}/events`);
|
|
21
26
|
return result.events;
|
|
22
27
|
}
|
|
23
|
-
export async function listOutputs(http,
|
|
24
|
-
const result = await http.request(`/api/runs/${encodeURIComponent(runId)}/outputs
|
|
28
|
+
export async function listOutputs(http, runId) {
|
|
29
|
+
const result = await http.request(`/api/runs/${encodeURIComponent(runId)}/outputs`);
|
|
25
30
|
return result.outputs;
|
|
26
31
|
}
|
|
27
|
-
export async function createOutputLink(http,
|
|
28
|
-
return http.request(`/api/runs/${encodeURIComponent(runId)}/outputs/${encodeURIComponent(outputId)}/link`, { method: "POST" }
|
|
32
|
+
export async function createOutputLink(http, runId, outputId) {
|
|
33
|
+
return http.request(`/api/runs/${encodeURIComponent(runId)}/outputs/${encodeURIComponent(outputId)}/link`, { method: "POST" });
|
|
29
34
|
}
|
|
30
|
-
export async function cancelRun(http,
|
|
31
|
-
await http.request(`/api/runs/${encodeURIComponent(runId)}/cancel`, { method: "POST" }
|
|
35
|
+
export async function cancelRun(http, runId) {
|
|
36
|
+
await http.request(`/api/runs/${encodeURIComponent(runId)}/cancel`, { method: "POST" });
|
|
32
37
|
}
|
|
33
|
-
export async function deleteRun(http,
|
|
34
|
-
await http.request(`/api/runs/${encodeURIComponent(runId)}`, { method: "DELETE" }
|
|
38
|
+
export async function deleteRun(http, runId) {
|
|
39
|
+
await http.request(`/api/runs/${encodeURIComponent(runId)}`, { method: "DELETE" });
|
|
35
40
|
}
|
|
36
41
|
export async function whoami(http) {
|
|
37
42
|
return http.request("/api/whoami");
|
|
38
43
|
}
|
|
44
|
+
// ===========================================================================
|
|
45
|
+
// Flat (Skill / McpServer / Blueprint) operations
|
|
46
|
+
// ===========================================================================
|
|
47
|
+
export async function submitRunFlat(http, request) {
|
|
48
|
+
return http.request("/api/runs", {
|
|
49
|
+
method: "POST",
|
|
50
|
+
body: JSON.stringify(request)
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Upload a workspace skill bundle as a zip blob. The dashboard BFF runs
|
|
55
|
+
* the two-phase flow internally (insert pending row, stream bytes into
|
|
56
|
+
* Supabase Storage, validate manifest, transition to ready) and returns
|
|
57
|
+
* the finalized `Skill`. Use `Skill.fromPath` / `Skill.upload` in the
|
|
58
|
+
* SDK to build the body; this transport function only knows about
|
|
59
|
+
* bytes.
|
|
60
|
+
*/
|
|
61
|
+
export async function createSkillBundle(http, args) {
|
|
62
|
+
const form = new FormData();
|
|
63
|
+
form.append("name", args.name);
|
|
64
|
+
const blobBody = toBlob(args.body, args.contentType ?? "application/zip");
|
|
65
|
+
form.append("bundle", blobBody, args.filename ?? `${args.name}.zip`);
|
|
66
|
+
return http.request("/api/skills", {
|
|
67
|
+
method: "POST",
|
|
68
|
+
body: form
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export async function listSkills(http) {
|
|
72
|
+
const result = await http.request("/api/skills");
|
|
73
|
+
if (Array.isArray(result)) {
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
return result.skills;
|
|
77
|
+
}
|
|
78
|
+
export async function getSkill(http, skillId) {
|
|
79
|
+
return http.request(`/api/skills/${encodeURIComponent(skillId)}`);
|
|
80
|
+
}
|
|
81
|
+
export async function deleteSkill(http, skillId) {
|
|
82
|
+
await http.request(`/api/skills/${encodeURIComponent(skillId)}`, {
|
|
83
|
+
method: "DELETE"
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function toBlob(input, contentType) {
|
|
87
|
+
if (input instanceof Blob) {
|
|
88
|
+
return input;
|
|
89
|
+
}
|
|
90
|
+
if (input instanceof Uint8Array) {
|
|
91
|
+
// BlobPart accepts ArrayBufferView, but lib.dom's overload set
|
|
92
|
+
// narrows on the underlying buffer kind. Slice into a fresh
|
|
93
|
+
// ArrayBuffer so a SharedArrayBuffer-backed Uint8Array works.
|
|
94
|
+
const copy = new Uint8Array(input.byteLength);
|
|
95
|
+
copy.set(input);
|
|
96
|
+
return new Blob([copy.buffer], { type: contentType });
|
|
97
|
+
}
|
|
98
|
+
return new Blob([input], { type: contentType });
|
|
99
|
+
}
|
|
39
100
|
function hasRun(value) {
|
|
40
101
|
return Boolean(value && typeof value === "object" && "run" in value);
|
|
41
102
|
}
|
|
@@ -73,6 +73,36 @@ export interface WhoAmI {
|
|
|
73
73
|
readonly scopes?: readonly string[];
|
|
74
74
|
readonly [key: string]: unknown;
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Workspace skill bundle as the dashboard BFF returns it. Mirrors a row
|
|
78
|
+
* of `skill_bundles` joined with its computed manifest. `state` is the
|
|
79
|
+
* upload lifecycle (`pending` -> `ready`); only `ready` rows are
|
|
80
|
+
* referenceable from a run. `deletedAt` is the soft-delete tombstone
|
|
81
|
+
* (`null` for live bundles).
|
|
82
|
+
*
|
|
83
|
+
* See `references/repo-architecture.md` (Skill custody) and
|
|
84
|
+
* supabase/migrations/20260512000000_skill_bundles.sql for the
|
|
85
|
+
* authoritative shape.
|
|
86
|
+
*/
|
|
87
|
+
export interface Skill {
|
|
88
|
+
readonly id: string;
|
|
89
|
+
readonly workspaceId?: string;
|
|
90
|
+
readonly name: string;
|
|
91
|
+
readonly state: "pending" | "ready";
|
|
92
|
+
readonly hash?: string | null;
|
|
93
|
+
readonly sizeBytes?: number | null;
|
|
94
|
+
readonly fileCount?: number | null;
|
|
95
|
+
readonly manifest?: ReadonlyArray<{
|
|
96
|
+
readonly path: string;
|
|
97
|
+
readonly size: number;
|
|
98
|
+
readonly mode: number;
|
|
99
|
+
}>;
|
|
100
|
+
readonly createdAt?: string;
|
|
101
|
+
readonly updatedAt?: string;
|
|
102
|
+
readonly finalizedAt?: string | null;
|
|
103
|
+
readonly deletedAt?: string | null;
|
|
104
|
+
readonly [key: string]: unknown;
|
|
105
|
+
}
|
|
76
106
|
/**
|
|
77
107
|
* Full submission as the SDK and CLI assemble it before posting. The
|
|
78
108
|
* `template` is the SDK-level ResolvedTemplate (or a JSON-encoded one);
|
package/dist/_shared/stable.d.ts
CHANGED
|
@@ -1,2 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical hosted antpath URL. Used as the default `baseUrl` for the
|
|
3
|
+
* SDK `AntpathClient` and the host-side CLI `--dashboard-url` flag.
|
|
4
|
+
*
|
|
5
|
+
* A single canonical default is not a "footnote mode" — it is the
|
|
6
|
+
* canonical product. Self-hosted deployments override via the
|
|
7
|
+
* explicit `baseUrl` / `--dashboard-url` parameter. The value lives in
|
|
8
|
+
* source (no env-var override) so the agent reading the SDK call site
|
|
9
|
+
* can see exactly where the call goes.
|
|
10
|
+
*
|
|
11
|
+
* See `references/development-principles.md` (Agent-first surface
|
|
12
|
+
* design, Concrete rule 3).
|
|
13
|
+
*/
|
|
14
|
+
export declare const ANTPATH_DEFAULT_BASE_URL = "https://antpath.ai";
|
|
1
15
|
export declare function stableStringify(value: unknown): string;
|
|
2
16
|
export declare function sha256(value: unknown): string;
|
package/dist/_shared/stable.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Canonical hosted antpath URL. Used as the default `baseUrl` for the
|
|
4
|
+
* SDK `AntpathClient` and the host-side CLI `--dashboard-url` flag.
|
|
5
|
+
*
|
|
6
|
+
* A single canonical default is not a "footnote mode" — it is the
|
|
7
|
+
* canonical product. Self-hosted deployments override via the
|
|
8
|
+
* explicit `baseUrl` / `--dashboard-url` parameter. The value lives in
|
|
9
|
+
* source (no env-var override) so the agent reading the SDK call site
|
|
10
|
+
* can see exactly where the call goes.
|
|
11
|
+
*
|
|
12
|
+
* See `references/development-principles.md` (Agent-first surface
|
|
13
|
+
* design, Concrete rule 3).
|
|
14
|
+
*/
|
|
15
|
+
export const ANTPATH_DEFAULT_BASE_URL = "https://antpath.ai";
|
|
2
16
|
export function stableStringify(value) {
|
|
3
17
|
return JSON.stringify(sortValue(value));
|
|
4
18
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ProxyAuthShape, type ProxyMethod, type ProxyResponseMode } from "./proxy-protocol.js";
|
|
2
|
+
import type { McpServerRef, SkillRef } from "./blueprint.js";
|
|
2
3
|
export type JsonPrimitive = string | number | boolean | null;
|
|
3
4
|
export type JsonValue = JsonPrimitive | JsonValue[] | {
|
|
4
5
|
readonly [key: string]: JsonValue;
|
|
@@ -125,6 +126,21 @@ export interface PlatformRunSubmissionRequest {
|
|
|
125
126
|
*/
|
|
126
127
|
readonly proxyEndpoints?: readonly PlatformProxyEndpoint[];
|
|
127
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Wire shape posted by the SDK and CLI. `workspaceId` is **omitted by
|
|
131
|
+
* design** — token-authenticated clients never name the workspace
|
|
132
|
+
* because it is derived from their API token on the server. The BFF
|
|
133
|
+
* route resolves the workspace from the token and injects it before
|
|
134
|
+
* calling `parseRunSubmissionRequest`. The dashboard UI (Auth.js user
|
|
135
|
+
* principal, multi-workspace) is the only caller that supplies
|
|
136
|
+
* `workspaceId` itself.
|
|
137
|
+
*
|
|
138
|
+
* See `references/development-principles.md` (Agent-first surface
|
|
139
|
+
* design, Concrete rule 3).
|
|
140
|
+
*/
|
|
141
|
+
export type PlatformRunSubmissionInput = Omit<PlatformRunSubmissionRequest, "workspaceId"> & {
|
|
142
|
+
readonly workspaceId?: string;
|
|
143
|
+
};
|
|
128
144
|
/**
|
|
129
145
|
* Default caps for a proxy endpoint when the submission doesn't specify
|
|
130
146
|
* one. Conservative on purpose. Operators can override the platform-
|
|
@@ -140,3 +156,42 @@ export declare const PROXY_ENDPOINT_DEFAULTS: {
|
|
|
140
156
|
readonly responseByteBudget: number;
|
|
141
157
|
};
|
|
142
158
|
export declare function parseRunSubmissionRequest(input: unknown): PlatformRunSubmissionRequest;
|
|
159
|
+
/**
|
|
160
|
+
* Wire-level submission posted to /api/runs in the flat surface. The
|
|
161
|
+
* `prompt` is always an array internally so the worker, the audit log,
|
|
162
|
+
* and the BFF idempotency hash all see one shape. `mcpServers` carries
|
|
163
|
+
* only the non-secret half; bearer headers travel in
|
|
164
|
+
* `secrets.mcpServers` keyed by `name`.
|
|
165
|
+
*
|
|
166
|
+
* `skills` is a list of `SkillRef`s — workspace refs point at
|
|
167
|
+
* `skill_bundles.id` (validated by the BFF before acceptance and pinned
|
|
168
|
+
* into `run_skill_snapshots`), provider refs pass through unchanged.
|
|
169
|
+
*/
|
|
170
|
+
export interface PlatformFlatSubmission {
|
|
171
|
+
readonly model: string;
|
|
172
|
+
readonly system?: string;
|
|
173
|
+
readonly prompt: readonly string[];
|
|
174
|
+
readonly skills: readonly SkillRef[];
|
|
175
|
+
readonly mcpServers: readonly McpServerRef[];
|
|
176
|
+
readonly environment?: PlatformTemplateEnvironment;
|
|
177
|
+
readonly metadata?: Record<string, JsonValue>;
|
|
178
|
+
}
|
|
179
|
+
export interface PlatformFlatRunSubmissionRequest {
|
|
180
|
+
readonly workspaceId: string;
|
|
181
|
+
readonly idempotencyKey: string;
|
|
182
|
+
readonly submission: PlatformFlatSubmission;
|
|
183
|
+
readonly cleanup?: PlatformCleanupPolicy;
|
|
184
|
+
readonly secrets: PlatformInlineSecrets;
|
|
185
|
+
readonly proxyEndpoints?: readonly PlatformProxyEndpoint[];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Same `workspaceId is optional` rule as the template-path
|
|
189
|
+
* `PlatformRunSubmissionInput`: token-authenticated clients leave it
|
|
190
|
+
* out, the BFF route injects it from the token before invoking the
|
|
191
|
+
* parser. Dashboard UI callers (Auth.js user principal) pass it
|
|
192
|
+
* explicitly.
|
|
193
|
+
*/
|
|
194
|
+
export type PlatformFlatRunSubmissionInput = Omit<PlatformFlatRunSubmissionRequest, "workspaceId"> & {
|
|
195
|
+
readonly workspaceId?: string;
|
|
196
|
+
};
|
|
197
|
+
export declare function parseFlatRunSubmissionRequest(input: unknown): PlatformFlatRunSubmissionRequest;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { authShapeHeaderName, PROXY_ALLOWED_METHODS, PROXY_RESPONSE_MODES } from "./proxy-protocol.js";
|
|
2
|
+
import { parseMcpServerRef, parseSkillRef } from "./blueprint.js";
|
|
2
3
|
const SECRETS_KEY = "secrets";
|
|
3
4
|
/**
|
|
4
5
|
* Default caps for a proxy endpoint when the submission doesn't specify
|
|
@@ -459,7 +460,15 @@ function parseMcpServers(input) {
|
|
|
459
460
|
if (!Array.isArray(input)) {
|
|
460
461
|
throw new Error("secrets.mcpServers must be an array");
|
|
461
462
|
}
|
|
462
|
-
|
|
463
|
+
const seen = new Set();
|
|
464
|
+
return input.map((entry, index) => {
|
|
465
|
+
const parsed = parseMcpServer(entry, `secrets.mcpServers[${index}]`);
|
|
466
|
+
if (seen.has(parsed.name)) {
|
|
467
|
+
throw new Error(`secrets.mcpServers duplicate name: ${parsed.name}`);
|
|
468
|
+
}
|
|
469
|
+
seen.add(parsed.name);
|
|
470
|
+
return parsed;
|
|
471
|
+
});
|
|
463
472
|
}
|
|
464
473
|
function parseMcpServer(input, path) {
|
|
465
474
|
const value = requireRecord(input, path);
|
|
@@ -678,4 +687,129 @@ function isJsonValue(input) {
|
|
|
678
687
|
}
|
|
679
688
|
return false;
|
|
680
689
|
}
|
|
690
|
+
export function parseFlatRunSubmissionRequest(input) {
|
|
691
|
+
const value = requireRecord(input, "submission");
|
|
692
|
+
// Defence in depth: scan every non-secrets field for credential-named
|
|
693
|
+
// keys, exactly like `parseRunSubmissionRequest`. The `secrets` key is
|
|
694
|
+
// the only allow-listed home for credential material.
|
|
695
|
+
for (const [key, fieldValue] of Object.entries(value)) {
|
|
696
|
+
if (key === SECRETS_KEY) {
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
if (deniedSecretFields.has(key)) {
|
|
700
|
+
throw new Error(`Secret-bearing field is not allowed in platform submission: ${key}`);
|
|
701
|
+
}
|
|
702
|
+
assertNoSecretBearingFields(fieldValue, [key]);
|
|
703
|
+
}
|
|
704
|
+
const cleanup = parseCleanupPolicy(value.cleanup);
|
|
705
|
+
const proxyEndpoints = parseProxyEndpoints(value.proxyEndpoints);
|
|
706
|
+
const secrets = parseInlineSecrets(value.secrets);
|
|
707
|
+
crossValidateProxyEndpointsAndAuth(proxyEndpoints, secrets.proxyEndpointAuth);
|
|
708
|
+
const submission = parseFlatSubmission(value.submission);
|
|
709
|
+
// mcpServers names must agree across the submission half and the
|
|
710
|
+
// secrets half — every secrets.mcpServers[i].name MUST resolve to a
|
|
711
|
+
// submission.mcpServers entry (no orphan secrets) AND the URL must
|
|
712
|
+
// match exactly. The reverse is allowed (an MCP server with no auth
|
|
713
|
+
// headers is a valid public-MCP mode).
|
|
714
|
+
if (secrets.mcpServers !== undefined) {
|
|
715
|
+
const declared = new Map(submission.mcpServers.map((m) => [m.name, m.url]));
|
|
716
|
+
for (const secret of secrets.mcpServers) {
|
|
717
|
+
const declaredUrl = declared.get(secret.name);
|
|
718
|
+
if (declaredUrl === undefined) {
|
|
719
|
+
throw new Error(`secrets.mcpServers[name=${secret.name}] has no matching submission.mcpServers entry`);
|
|
720
|
+
}
|
|
721
|
+
if (declaredUrl !== secret.url) {
|
|
722
|
+
throw new Error(`secrets.mcpServers[name=${secret.name}].url must equal submission.mcpServers[name=${secret.name}].url ` +
|
|
723
|
+
`(got submission=${declaredUrl}, secrets=${secret.url})`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return {
|
|
728
|
+
workspaceId: requireString(value.workspaceId, "workspaceId"),
|
|
729
|
+
idempotencyKey: requireString(value.idempotencyKey, "idempotencyKey"),
|
|
730
|
+
submission,
|
|
731
|
+
...(cleanup ? { cleanup } : {}),
|
|
732
|
+
...(proxyEndpoints ? { proxyEndpoints } : {}),
|
|
733
|
+
secrets
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function parseFlatSubmission(input) {
|
|
737
|
+
const value = requireRecord(input, "submission.submission");
|
|
738
|
+
const allowed = new Set([
|
|
739
|
+
"model",
|
|
740
|
+
"system",
|
|
741
|
+
"prompt",
|
|
742
|
+
"skills",
|
|
743
|
+
"mcpServers",
|
|
744
|
+
"environment",
|
|
745
|
+
"metadata"
|
|
746
|
+
]);
|
|
747
|
+
for (const key of Object.keys(value)) {
|
|
748
|
+
if (!allowed.has(key)) {
|
|
749
|
+
throw new Error(`submission.${key} is not an allowed field; permitted: ${[...allowed].join(", ")}`);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
const model = requireString(value.model, "submission.model");
|
|
753
|
+
const system = optionalString(value.system, "submission.system");
|
|
754
|
+
const prompt = parseFlatPrompt(value.prompt);
|
|
755
|
+
const skills = parseFlatSkills(value.skills);
|
|
756
|
+
const mcpServers = parseFlatMcpServers(value.mcpServers);
|
|
757
|
+
const environment = parseTemplateEnvironment(value.environment);
|
|
758
|
+
const metadata = optionalJsonRecord(value.metadata, "submission.metadata");
|
|
759
|
+
return {
|
|
760
|
+
model,
|
|
761
|
+
...(system ? { system } : {}),
|
|
762
|
+
prompt,
|
|
763
|
+
skills,
|
|
764
|
+
mcpServers,
|
|
765
|
+
...(environment ? { environment } : {}),
|
|
766
|
+
...(metadata ? { metadata } : {})
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function parseFlatPrompt(input) {
|
|
770
|
+
if (typeof input === "string") {
|
|
771
|
+
if (input.length === 0) {
|
|
772
|
+
throw new Error("submission.prompt must be non-empty");
|
|
773
|
+
}
|
|
774
|
+
return [input];
|
|
775
|
+
}
|
|
776
|
+
if (!Array.isArray(input)) {
|
|
777
|
+
throw new Error("submission.prompt must be a string or an array of strings");
|
|
778
|
+
}
|
|
779
|
+
if (input.length === 0) {
|
|
780
|
+
throw new Error("submission.prompt array must be non-empty");
|
|
781
|
+
}
|
|
782
|
+
return input.map((item, index) => {
|
|
783
|
+
if (typeof item !== "string" || item.length === 0) {
|
|
784
|
+
throw new Error(`submission.prompt[${index}] must be a non-empty string`);
|
|
785
|
+
}
|
|
786
|
+
return item;
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
function parseFlatSkills(input) {
|
|
790
|
+
if (input === undefined) {
|
|
791
|
+
return [];
|
|
792
|
+
}
|
|
793
|
+
if (!Array.isArray(input)) {
|
|
794
|
+
throw new Error("submission.skills must be an array of SkillRef objects");
|
|
795
|
+
}
|
|
796
|
+
return input.map((item, index) => parseSkillRef(item, `submission.skills[${index}]`));
|
|
797
|
+
}
|
|
798
|
+
function parseFlatMcpServers(input) {
|
|
799
|
+
if (input === undefined) {
|
|
800
|
+
return [];
|
|
801
|
+
}
|
|
802
|
+
if (!Array.isArray(input)) {
|
|
803
|
+
throw new Error("submission.mcpServers must be an array of {name, url} objects");
|
|
804
|
+
}
|
|
805
|
+
const seen = new Set();
|
|
806
|
+
return input.map((item, index) => {
|
|
807
|
+
const ref = parseMcpServerRef(item, `submission.mcpServers[${index}]`);
|
|
808
|
+
if (seen.has(ref.name)) {
|
|
809
|
+
throw new Error(`submission.mcpServers duplicate name: ${ref.name}`);
|
|
810
|
+
}
|
|
811
|
+
seen.add(ref.name);
|
|
812
|
+
return ref;
|
|
813
|
+
});
|
|
814
|
+
}
|
|
681
815
|
//# sourceMappingURL=submission.js.map
|