@aexhq/sdk 0.29.0 → 0.31.0
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 +95 -8
- package/dist/_contracts/connection-ticket.d.ts +1 -1
- package/dist/_contracts/connection-ticket.js +1 -1
- package/dist/_contracts/event-envelope.d.ts +5 -8
- package/dist/_contracts/event-envelope.js +5 -6
- package/dist/_contracts/event-guards.d.ts +67 -0
- package/dist/_contracts/event-guards.js +36 -0
- package/dist/_contracts/event-stream-client.d.ts +1 -1
- package/dist/_contracts/http.js +1 -1
- package/dist/_contracts/index.d.ts +2 -0
- package/dist/_contracts/index.js +6 -0
- package/dist/_contracts/operations.d.ts +2 -47
- package/dist/_contracts/operations.js +7 -112
- package/dist/_contracts/provider-support.d.ts +48 -138
- package/dist/_contracts/provider-support.js +10 -41
- package/dist/_contracts/proxy-protocol.d.ts +7 -7
- package/dist/_contracts/proxy-protocol.js +8 -8
- package/dist/_contracts/run-config.d.ts +7 -20
- package/dist/_contracts/run-config.js +8 -46
- package/dist/_contracts/run-cost.d.ts +1 -5
- package/dist/_contracts/run-cost.js +0 -8
- package/dist/_contracts/run-custody.d.ts +4 -6
- package/dist/_contracts/run-custody.js +0 -8
- package/dist/_contracts/run-trace.d.ts +7 -0
- package/dist/_contracts/run-trace.js +9 -0
- package/dist/_contracts/run-unit.d.ts +1 -1
- package/dist/_contracts/run-unit.js +2 -2
- package/dist/_contracts/runner-event.d.ts +1 -1
- package/dist/_contracts/runner-event.js +1 -1
- package/dist/_contracts/runtime-manifest.d.ts +13 -26
- package/dist/_contracts/runtime-manifest.js +6 -35
- package/dist/_contracts/runtime-types.d.ts +32 -1
- package/dist/_contracts/sdk-secrets.js +4 -4
- package/dist/_contracts/side-effect-audit.d.ts +2 -4
- package/dist/_contracts/side-effect-audit.js +2 -4
- package/dist/_contracts/status.d.ts +1 -1
- package/dist/_contracts/status.js +1 -1
- package/dist/_contracts/submission.d.ts +19 -126
- package/dist/_contracts/submission.js +31 -185
- package/dist/_contracts/webhook-verify.d.ts +1 -1
- package/dist/_contracts/webhook-verify.js +1 -1
- package/dist/agents-md.d.ts +4 -1
- package/dist/agents-md.js +10 -9
- package/dist/agents-md.js.map +1 -1
- package/dist/asset-upload.d.ts +4 -10
- package/dist/asset-upload.js +4 -47
- package/dist/asset-upload.js.map +1 -1
- package/dist/cli.mjs +17647 -3950
- package/dist/cli.mjs.sha256 +1 -1
- package/dist/client.d.ts +79 -61
- package/dist/client.js +207 -125
- package/dist/client.js.map +1 -1
- package/dist/data-tools.d.ts +23 -0
- package/dist/data-tools.js +102 -13
- package/dist/data-tools.js.map +1 -1
- package/dist/file.d.ts +4 -1
- package/dist/file.js +10 -9
- package/dist/file.js.map +1 -1
- package/dist/index.d.ts +9 -8
- package/dist/index.js +10 -8
- package/dist/index.js.map +1 -1
- package/dist/skill.d.ts +9 -7
- package/dist/skill.js +15 -15
- package/dist/skill.js.map +1 -1
- package/dist/tool.d.ts +4 -1
- package/dist/tool.js +10 -8
- package/dist/tool.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/cleanup.md +2 -2
- package/docs/concepts/agent-tools.md +9 -5
- package/docs/concepts/composition.md +1 -1
- package/docs/concepts/providers-and-runtimes.md +2 -4
- package/docs/concepts/runs.md +3 -6
- package/docs/credentials.md +2 -5
- package/docs/defaults.md +22 -22
- package/docs/events.md +32 -9
- package/docs/limits-and-quotas.md +40 -40
- package/docs/limits.md +1 -1
- package/docs/networking.md +141 -0
- package/docs/outputs.md +1 -1
- package/docs/provider-runtime-capabilities.md +36 -64
- package/docs/public-surface.json +2 -3
- package/docs/quickstart.md +32 -11
- package/docs/run-config.md +3 -4
- package/docs/secrets.md +7 -5
- package/docs/skills.md +4 -12
- package/docs/vision-skills.md +1 -1
- package/examples/chat-corpus.ts +85 -0
- package/package.json +2 -2
package/dist/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AexError,
|
|
1
|
+
import { AexError, DEFAULT_RUN_PROVIDER, HttpClient, RunConfigValidationError, RunStateError, SecretString, isRunSettled, operations, providersForModel, streamCoordinatorEvents, summarizeRunTrace, textOf, parseRunLimits, BUILTIN_TOOL_NAMES, TERMINAL_RUN_STATUSES } from "./_contracts/index.js";
|
|
2
2
|
import { AgentsMd } from "./agents-md.js";
|
|
3
3
|
import { uploadAsset } from "./asset-upload.js";
|
|
4
4
|
import { File } from "./file.js";
|
|
@@ -55,18 +55,6 @@ export class SkillsClient {
|
|
|
55
55
|
findByName(name) {
|
|
56
56
|
return operations.findSkillByName(this.#http, name);
|
|
57
57
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Internal: post a pre-bundled skill zip to the BFF. Only
|
|
60
|
-
* `Skill.upload` calls this. NOT part of the public API.
|
|
61
|
-
*/
|
|
62
|
-
async _uploadSkillBundle(args) {
|
|
63
|
-
return operations.createSkillBundle(this.#http, {
|
|
64
|
-
name: args.name,
|
|
65
|
-
body: args.body,
|
|
66
|
-
contentType: "application/zip",
|
|
67
|
-
filename: `${args.name}.zip`
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
58
|
}
|
|
71
59
|
/**
|
|
72
60
|
* Workspace AgentsMd admin operations exposed under `client.agentsMd`.
|
|
@@ -230,21 +218,6 @@ export class AgentExecutor {
|
|
|
230
218
|
this.files = new FilesClient(this.#http);
|
|
231
219
|
this.secrets = new SecretsClient(this.#http);
|
|
232
220
|
}
|
|
233
|
-
/**
|
|
234
|
-
* Internal: forwards to `SkillsClient._uploadSkillBundle`. NOT part of
|
|
235
|
-
* the public API.
|
|
236
|
-
*
|
|
237
|
-
* NOTE (tech-debt): this is part of the legacy workspace-skill upload
|
|
238
|
-
* surface (`SkillsClient` + `operations.createSkillBundle` + the TUS
|
|
239
|
-
* chunked path in asset-upload.ts). The live submit path materializes
|
|
240
|
-
* inline skills via `uploadAsset` instead; `Skill.upload(client)`
|
|
241
|
-
* pre-stages a draft explicitly for reuse. This surface is retained
|
|
242
|
-
* pending a deliberate deprecation pass (it still threads into the CLI
|
|
243
|
-
* host commands), tracked in the remediation plan as item 4a.
|
|
244
|
-
*/
|
|
245
|
-
async _uploadSkillBundle(args) {
|
|
246
|
-
return this.skills._uploadSkillBundle(args);
|
|
247
|
-
}
|
|
248
221
|
/**
|
|
249
222
|
* Internal: an `AgentsMd.upload(this)` shortcut that bypasses
|
|
250
223
|
* `client.agentsMd` indirection. Forwarded to
|
|
@@ -286,17 +259,86 @@ export class AgentExecutor {
|
|
|
286
259
|
});
|
|
287
260
|
}
|
|
288
261
|
/**
|
|
289
|
-
* Submit a run
|
|
290
|
-
* "do it and give me the result"
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
262
|
+
* Submit a run, wait until its RECORD is terminal, and collect the full
|
|
263
|
+
* {@link RunResult} — the settle-consistent "do it and give me the result"
|
|
264
|
+
* primitive. Folds the poll loop every consumer hand-rolled into one call:
|
|
265
|
+
* submit → {@link waitForRun} (polls `getRun`, NOT the earlier RUN_FINISHED
|
|
266
|
+
* event) → poll `listEvents` until the snapshot is settle-bracketed
|
|
267
|
+
* (RUN_STARTED + a terminal event present) → `listOutputs` → decode the trace
|
|
268
|
+
* and assistant text. On resolve, `getRun`/`listOutputs` are guaranteed
|
|
269
|
+
* consistent.
|
|
270
|
+
*
|
|
271
|
+
* Uses polling (portable across backends), NOT the coordinator WebSocket. By
|
|
272
|
+
* default a failed run resolves with `ok: false` and a populated `error`; pass
|
|
273
|
+
* `{ throwOnFailure: true }` to throw instead. For live events prefer `submit`
|
|
274
|
+
* + `streamEnvelopes(runId, { settleConsistent: true })`.
|
|
296
275
|
*/
|
|
297
|
-
async run(options) {
|
|
276
|
+
async run(options, opts = {}) {
|
|
277
|
+
const signal = opts.signal ?? options.signal;
|
|
298
278
|
const runId = await this.submit(options);
|
|
299
|
-
|
|
279
|
+
const run = await this.waitForRun(runId, {
|
|
280
|
+
...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
|
|
281
|
+
...(signal ? { signal } : {})
|
|
282
|
+
});
|
|
283
|
+
const events = await this.#collectSettledEvents(runId, signal);
|
|
284
|
+
const outputs = await this.listOutputs(runId);
|
|
285
|
+
const ok = run.status === "succeeded";
|
|
286
|
+
const costUsd = run.costTelemetry?.billedCostUsd;
|
|
287
|
+
const errorMessage = typeof run.errorMessage === "string" && run.errorMessage ? run.errorMessage : undefined;
|
|
288
|
+
const result = {
|
|
289
|
+
runId,
|
|
290
|
+
run,
|
|
291
|
+
status: run.status,
|
|
292
|
+
ok,
|
|
293
|
+
text: textOf(events),
|
|
294
|
+
events,
|
|
295
|
+
trace: summarizeRunTrace(events),
|
|
296
|
+
outputs,
|
|
297
|
+
...(run.usage ? { usage: run.usage } : {}),
|
|
298
|
+
...(typeof costUsd === "number" ? { costUsd } : {}),
|
|
299
|
+
...(!ok && errorMessage ? { error: errorMessage } : {})
|
|
300
|
+
};
|
|
301
|
+
if (opts.throwOnFailure && !ok) {
|
|
302
|
+
throw new RunStateError(`AgentExecutor.run: run ${runId} ended ${run.status}${errorMessage ? `: ${errorMessage}` : ""}`, { runId, status: run.status });
|
|
303
|
+
}
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Explicit, discoverable alias for {@link run}: submit, wait, and collect the
|
|
308
|
+
* full {@link RunResult} in one call.
|
|
309
|
+
*/
|
|
310
|
+
runAndCollect(options, opts) {
|
|
311
|
+
return this.run(options, opts);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Poll `listEvents` until the snapshot is settle-bracketed — both a
|
|
315
|
+
* RUN_STARTED and a terminal (RUN_FINISHED / RUN_ERROR) event present — then
|
|
316
|
+
* return it. The runner emits the terminal AG-UI event BEFORE the platform
|
|
317
|
+
* commits the record, and the `listEvents` snapshot can lag the terminal
|
|
318
|
+
* record by a beat; this closes that race so the decoded trace/text/outputs
|
|
319
|
+
* are complete. Bounded so an older runtime that never emits one of the
|
|
320
|
+
* brackets still returns the best snapshot available.
|
|
321
|
+
*/
|
|
322
|
+
async #collectSettledEvents(runId, signal) {
|
|
323
|
+
const intervalMs = 500;
|
|
324
|
+
const maxAttempts = 20;
|
|
325
|
+
let latest = [];
|
|
326
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
327
|
+
if (signal?.aborted)
|
|
328
|
+
return latest;
|
|
329
|
+
latest = await this.listEvents(runId);
|
|
330
|
+
const hasStart = latest.some((event) => event.type === "RUN_STARTED");
|
|
331
|
+
const hasTerminal = latest.some((event) => event.type === "RUN_FINISHED" || event.type === "RUN_ERROR");
|
|
332
|
+
if (hasStart && hasTerminal)
|
|
333
|
+
return latest;
|
|
334
|
+
try {
|
|
335
|
+
await sleep(intervalMs, signal);
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
return latest;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return latest;
|
|
300
342
|
}
|
|
301
343
|
/**
|
|
302
344
|
* Submit a run and return its run id immediately. Use that id with
|
|
@@ -316,8 +358,9 @@ export class AgentExecutor {
|
|
|
316
358
|
*/
|
|
317
359
|
async submit(options) {
|
|
318
360
|
if (!options || typeof options !== "object") {
|
|
319
|
-
throw new
|
|
361
|
+
throw new RunConfigValidationError("AgentExecutor.submit: options is required");
|
|
320
362
|
}
|
|
363
|
+
assertNoRemovedSubmitFields(options);
|
|
321
364
|
// A model maps to one or more upstream providers (see MODEL_PROVIDER_IDS).
|
|
322
365
|
// `providersForModel` returns the supported providers in priority order, or
|
|
323
366
|
// `[]` for an unknown model string (the model check below then rejects it).
|
|
@@ -327,43 +370,20 @@ export class AgentExecutor {
|
|
|
327
370
|
if (options.provider &&
|
|
328
371
|
supportedProviders.length > 0 &&
|
|
329
372
|
!supportedProviders.includes(options.provider)) {
|
|
330
|
-
throw new
|
|
373
|
+
throw new RunConfigValidationError(`AgentExecutor.submit: provider ${JSON.stringify(options.provider)} is not available for ` +
|
|
331
374
|
`model ${JSON.stringify(options.model)} (supported: ${supportedProviders.join(", ")})`);
|
|
332
375
|
}
|
|
333
376
|
const provider = options.provider ?? supportedProviders[0] ?? DEFAULT_RUN_PROVIDER;
|
|
334
|
-
|
|
335
|
-
if (!options.secrets) {
|
|
336
|
-
throw new Error("AgentExecutor.submit: secrets is required");
|
|
337
|
-
}
|
|
338
|
-
// The BYOK provider key (for the selected `provider`) is required. The
|
|
339
|
-
// shared parser re-runs this check on the server; failing early here
|
|
340
|
-
// gives the caller a synchronous error before any network call.
|
|
341
|
-
const selectedProviderKey = options.secrets.apiKeys?.[provider] ?? options.secrets.apiKey;
|
|
342
|
-
if (typeof selectedProviderKey !== "string" || !selectedProviderKey) {
|
|
343
|
-
throw new Error("AgentExecutor.submit: secrets.apiKey is required");
|
|
344
|
-
}
|
|
377
|
+
validateSubmitCredentials(options, provider);
|
|
345
378
|
if (typeof options.model !== "string" || !options.model) {
|
|
346
|
-
throw new
|
|
379
|
+
throw new RunConfigValidationError("AgentExecutor.submit: model is required");
|
|
347
380
|
}
|
|
348
381
|
const prompt = normalisePrompt(options.prompt);
|
|
349
382
|
const { endpoints: proxyEndpointDeclarations, auth: proxyEndpointAuthFromInstances } = splitProxyEndpoints(options.proxyEndpoints ?? []);
|
|
350
|
-
const mergedProxyAuth = mergeProxyEndpointAuth(proxyEndpointAuthFromInstances, options.secrets
|
|
383
|
+
const mergedProxyAuth = mergeProxyEndpointAuth(proxyEndpointAuthFromInstances, options.secrets?.proxyEndpointAuth ?? []);
|
|
351
384
|
// Split secretEnv into value-free declarations (hashed submission) and
|
|
352
385
|
// ephemeral values (vaulted secrets channel), mirroring the proxy split.
|
|
353
386
|
const { declarations: secretEnvDeclarations, values: envSecretValues } = splitSecretEnv(options.secretEnv);
|
|
354
|
-
// Validate the runtime selector before any network I/O — inline drafts
|
|
355
|
-
// are uploaded below, so an invalid runtime must reject first rather than
|
|
356
|
-
// leak an asset upload.
|
|
357
|
-
if (options.runtime !== undefined &&
|
|
358
|
-
!RUNTIME_KINDS.includes(options.runtime)) {
|
|
359
|
-
throw new AexError("RUNTIME_UNSUPPORTED", `AgentExecutor.submit: runtime must be one of: ${RUNTIME_KINDS.join(", ")} ` +
|
|
360
|
-
`(got ${JSON.stringify(options.runtime)})`);
|
|
361
|
-
}
|
|
362
|
-
if (options.region !== undefined &&
|
|
363
|
-
!REGIONS.includes(options.region)) {
|
|
364
|
-
throw new AexError("RUN_CONFIG_INVALID", `AgentExecutor.submit: region must be one of: ${REGIONS.join(", ")} ` +
|
|
365
|
-
`(got ${JSON.stringify(options.region)})`);
|
|
366
|
-
}
|
|
367
387
|
// Validate the per-run limits override with the SAME parser the server runs
|
|
368
388
|
// (shape + positivity + allow-list), failing fast before any asset upload.
|
|
369
389
|
// Normalizes an all-absent override (e.g. `{}`) away.
|
|
@@ -383,7 +403,7 @@ export class AgentExecutor {
|
|
|
383
403
|
const preparedTools = await prepareTools(options.tools ?? [], uploader);
|
|
384
404
|
const preparedAgentsMd = await prepareAgentsMd(options.agentsMd ?? [], uploader);
|
|
385
405
|
const preparedFiles = await prepareFiles(options.files ?? [], uploader);
|
|
386
|
-
const { submissionMcpServers, mergedMcpSecrets } = mergeMcpServers(options.mcpServers ?? [], options.secrets
|
|
406
|
+
const { submissionMcpServers, mergedMcpSecrets } = mergeMcpServers(options.mcpServers ?? [], options.secrets?.mcpServers ?? []);
|
|
387
407
|
const outputCapture = outputsForWire(options.outputs);
|
|
388
408
|
const submission = {
|
|
389
409
|
model: options.model,
|
|
@@ -436,12 +456,6 @@ export class AgentExecutor {
|
|
|
436
456
|
// shared parser still defaults to `anthropic` when callers omit
|
|
437
457
|
// the field entirely, but the SDK has resolved it by here.
|
|
438
458
|
provider,
|
|
439
|
-
...(credentialMode !== DEFAULT_CREDENTIAL_MODE ? { credentialMode } : {}),
|
|
440
|
-
// `runtime` is optional on the wire — absent means let the
|
|
441
|
-
// dispatcher auto-route. Only emit it when the caller asked for
|
|
442
|
-
// a specific runtime so the wire shape stays minimal.
|
|
443
|
-
...(options.runtime ? { runtime: options.runtime } : {}),
|
|
444
|
-
...(options.region ? { region: options.region } : {}),
|
|
445
459
|
submission,
|
|
446
460
|
...(options.runtimeSize ? { runtimeSize: options.runtimeSize } : {}),
|
|
447
461
|
...(options.timeout ? { timeout: options.timeout } : {}),
|
|
@@ -483,6 +497,60 @@ export class AgentExecutor {
|
|
|
483
497
|
listRuns(query) {
|
|
484
498
|
return operations.listRuns(this.#http, query);
|
|
485
499
|
}
|
|
500
|
+
/**
|
|
501
|
+
* Find output files across runs by filename / extension / content type.
|
|
502
|
+
* Returns lean REFERENCE hits (runId, outputId, filename, size, content type)
|
|
503
|
+
* — never bytes; fetch content with {@link readOutputText}. Scope the search
|
|
504
|
+
* to a corpus with `query.runIds`; omit it to scan the whole workspace (needs
|
|
505
|
+
* the owner-gated `listRuns`). Composed client-side for the MVP (per-run
|
|
506
|
+
* `listOutputs` + the contracts output filter), so it works against any
|
|
507
|
+
* already-terminal run with no new server endpoint. Bounded by
|
|
508
|
+
* `query.limit` (default 100) — for very large corpora prefer the deferred
|
|
509
|
+
* server-side index.
|
|
510
|
+
*/
|
|
511
|
+
async searchOutputs(query = {}) {
|
|
512
|
+
const runIds = query.runIds ?? (await this.#allWorkspaceRunIds());
|
|
513
|
+
const limit = query.limit ?? 100;
|
|
514
|
+
// Translate the search query to an OutputQuery so the contracts output
|
|
515
|
+
// filter (classifyOutput / basename match / contentType wildcard) does the
|
|
516
|
+
// matching — no re-derived filter logic here.
|
|
517
|
+
const outputQuery = {
|
|
518
|
+
...(query.filename ? { filename: new RegExp(escapeRegExp(query.filename), "i") } : {}),
|
|
519
|
+
...(query.extension ? { extension: query.extension } : {}),
|
|
520
|
+
...(query.contentType ? { contentType: query.contentType } : {})
|
|
521
|
+
};
|
|
522
|
+
const hasFilter = Object.keys(outputQuery).length > 0;
|
|
523
|
+
const hits = [];
|
|
524
|
+
for (const runId of runIds) {
|
|
525
|
+
const outputs = hasFilter
|
|
526
|
+
? await this.listOutputs(runId, outputQuery)
|
|
527
|
+
: await this.listOutputs(runId);
|
|
528
|
+
for (const o of outputs) {
|
|
529
|
+
hits.push({
|
|
530
|
+
runId,
|
|
531
|
+
outputId: o.id,
|
|
532
|
+
...(o.filename !== undefined ? { filename: o.filename } : {}),
|
|
533
|
+
...(o.sizeBytes !== undefined ? { sizeBytes: o.sizeBytes } : {}),
|
|
534
|
+
...(o.contentType !== undefined ? { contentType: o.contentType } : {})
|
|
535
|
+
});
|
|
536
|
+
if (hits.length >= limit)
|
|
537
|
+
return { hits };
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return { hits };
|
|
541
|
+
}
|
|
542
|
+
/** Enumerate every run id in the workspace by paging `listRuns`. */
|
|
543
|
+
async #allWorkspaceRunIds() {
|
|
544
|
+
const ids = [];
|
|
545
|
+
let cursor;
|
|
546
|
+
do {
|
|
547
|
+
const page = await this.listRuns(cursor ? { cursor } : {});
|
|
548
|
+
for (const run of page.runs)
|
|
549
|
+
ids.push(run.id);
|
|
550
|
+
cursor = page.nextCursor;
|
|
551
|
+
} while (cursor);
|
|
552
|
+
return ids;
|
|
553
|
+
}
|
|
486
554
|
/**
|
|
487
555
|
* Fetch the self-contained `RunUnit`: parsed submission inputs,
|
|
488
556
|
* attempts, indexed events (inline + cursor for the tail), raw
|
|
@@ -526,7 +594,7 @@ export class AgentExecutor {
|
|
|
526
594
|
}
|
|
527
595
|
/**
|
|
528
596
|
* Stream the unified {@link AexEvent} envelope live over the coordinator
|
|
529
|
-
* WebSocket. The
|
|
597
|
+
* WebSocket. The hosted API's ticket broker authorizes the connection (workspace
|
|
530
598
|
* token → short-lived coordinator ticket); the shared client replays from
|
|
531
599
|
* the cursor, tails live, and resumes exactly-once across reconnects. The
|
|
532
600
|
* ticket is re-minted on each (re)connect so a long run never outlives it.
|
|
@@ -714,6 +782,10 @@ const TERMINAL_STATUSES = new Set(TERMINAL_RUN_STATUSES);
|
|
|
714
782
|
function isTerminal(status) {
|
|
715
783
|
return typeof status === "string" && TERMINAL_STATUSES.has(status);
|
|
716
784
|
}
|
|
785
|
+
/** Escape a literal string for safe interpolation into a RegExp. */
|
|
786
|
+
function escapeRegExp(input) {
|
|
787
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
788
|
+
}
|
|
717
789
|
function isOutputPathSelector(selector) {
|
|
718
790
|
return Boolean(selector && typeof selector === "object" && "path" in selector);
|
|
719
791
|
}
|
|
@@ -793,20 +865,41 @@ function generateIdempotencyKey() {
|
|
|
793
865
|
function normalisePrompt(input) {
|
|
794
866
|
if (typeof input === "string") {
|
|
795
867
|
if (!input) {
|
|
796
|
-
throw new
|
|
868
|
+
throw new RunConfigValidationError("AgentExecutor.submit: prompt must be a non-empty string");
|
|
797
869
|
}
|
|
798
870
|
return [input];
|
|
799
871
|
}
|
|
800
872
|
if (!Array.isArray(input) || input.length === 0) {
|
|
801
|
-
throw new
|
|
873
|
+
throw new RunConfigValidationError("AgentExecutor.submit: prompt must be a non-empty string or string array");
|
|
802
874
|
}
|
|
803
875
|
for (const segment of input) {
|
|
804
876
|
if (typeof segment !== "string" || !segment) {
|
|
805
|
-
throw new
|
|
877
|
+
throw new RunConfigValidationError("AgentExecutor.submit: prompt segments must be non-empty strings");
|
|
806
878
|
}
|
|
807
879
|
}
|
|
808
880
|
return [...input];
|
|
809
881
|
}
|
|
882
|
+
function assertNoRemovedSubmitFields(options) {
|
|
883
|
+
const record = options;
|
|
884
|
+
for (const field of ["credentialMode", "runtime", "region", "apiKey", "credentials"]) {
|
|
885
|
+
if (Object.prototype.hasOwnProperty.call(record, field)) {
|
|
886
|
+
throw new RunConfigValidationError(`AgentExecutor.submit: ${field} is not a supported option; use the managed path with secrets.apiKeys[provider].`);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
const secrets = record.secrets;
|
|
890
|
+
if (secrets && typeof secrets === "object" && !Array.isArray(secrets) && Object.prototype.hasOwnProperty.call(secrets, "apiKey")) {
|
|
891
|
+
throw new RunConfigValidationError("AgentExecutor.submit: secrets.apiKey is not supported; use secrets.apiKeys[provider].");
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
function validateSubmitCredentials(options, provider) {
|
|
895
|
+
if (options.parentRunId) {
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const key = options.secrets?.apiKeys?.[provider];
|
|
899
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
900
|
+
throw new RunConfigValidationError(`AgentExecutor.submit: a provider API key is required — pass secrets.apiKeys[${JSON.stringify(provider)}].`);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
810
903
|
function postHookForWire(input) {
|
|
811
904
|
if (input === undefined || typeof input.command !== "string" || input.command.trim().length === 0) {
|
|
812
905
|
return undefined;
|
|
@@ -835,31 +928,41 @@ function outputsForWire(outputs) {
|
|
|
835
928
|
...(outputs.maxFiles !== undefined ? { maxFiles: outputs.maxFiles } : {})
|
|
836
929
|
};
|
|
837
930
|
}
|
|
931
|
+
/**
|
|
932
|
+
* Resolve a draft's asset id: reuse the cached id from a prior submit, otherwise
|
|
933
|
+
* upload the bytes and cache the result on the instance.
|
|
934
|
+
*/
|
|
935
|
+
async function resolveAssetId(entry, bundle, uploader) {
|
|
936
|
+
const cached = entry._cachedAssetId;
|
|
937
|
+
if (cached !== undefined) {
|
|
938
|
+
return cached;
|
|
939
|
+
}
|
|
940
|
+
const uploaded = await uploader({
|
|
941
|
+
bytes: bundle.bytes,
|
|
942
|
+
hash: bundle.contentHash,
|
|
943
|
+
contentType: "application/zip"
|
|
944
|
+
});
|
|
945
|
+
entry._rememberAsset(uploaded.assetId);
|
|
946
|
+
return uploaded.assetId;
|
|
947
|
+
}
|
|
838
948
|
/** Walk Skill[], eagerly upload drafts as assets, and return plain asset refs. */
|
|
839
949
|
async function prepareSkills(skills, uploader) {
|
|
840
950
|
const refs = [];
|
|
841
951
|
for (let i = 0; i < skills.length; i++) {
|
|
842
952
|
const entry = skills[i];
|
|
843
953
|
if (!(entry instanceof Skill)) {
|
|
844
|
-
throw new
|
|
845
|
-
}
|
|
846
|
-
if (entry.isConsumed) {
|
|
847
|
-
throw new Error(`AgentExecutor.submit: skills[${i}] was already consumed by a prior submit`);
|
|
954
|
+
throw new RunConfigValidationError(`AgentExecutor.submit: skills[${i}] must be a Skill instance`);
|
|
848
955
|
}
|
|
849
956
|
const ref = entry.ref;
|
|
850
957
|
if (ref.kind === "draft") {
|
|
851
958
|
const bundle = entry._takeDraftBundle();
|
|
852
959
|
if (!bundle) {
|
|
853
|
-
throw new
|
|
960
|
+
throw new RunConfigValidationError(`AgentExecutor.submit: skills[${i}] is draft but has no bytes`);
|
|
854
961
|
}
|
|
855
|
-
const
|
|
856
|
-
bytes: bundle.bytes,
|
|
857
|
-
hash: bundle.contentHash,
|
|
858
|
-
contentType: "application/zip"
|
|
859
|
-
});
|
|
962
|
+
const assetId = await resolveAssetId(entry, bundle, uploader);
|
|
860
963
|
refs.push({
|
|
861
964
|
kind: "asset",
|
|
862
|
-
assetId
|
|
965
|
+
assetId,
|
|
863
966
|
name: bundle.name
|
|
864
967
|
});
|
|
865
968
|
continue;
|
|
@@ -885,7 +988,7 @@ async function prepareTools(tools, uploader) {
|
|
|
885
988
|
// A bare string is a builtin tool reference.
|
|
886
989
|
if (typeof entry === "string") {
|
|
887
990
|
if (!BUILTIN_TOOL_NAMES.includes(entry)) {
|
|
888
|
-
throw new
|
|
991
|
+
throw new RunConfigValidationError(`AgentExecutor.submit: tools[${i}] (${JSON.stringify(entry)}) is not a builtin tool name; ` +
|
|
889
992
|
`expected a Tool instance or one of: ${BUILTIN_TOOL_NAMES.join(", ")}`);
|
|
890
993
|
}
|
|
891
994
|
if (!seenBuiltins.has(entry)) {
|
|
@@ -895,23 +998,16 @@ async function prepareTools(tools, uploader) {
|
|
|
895
998
|
continue;
|
|
896
999
|
}
|
|
897
1000
|
if (!(entry instanceof Tool)) {
|
|
898
|
-
throw new
|
|
899
|
-
}
|
|
900
|
-
if (entry.isConsumed) {
|
|
901
|
-
throw new Error(`AgentExecutor.submit: tools[${i}] was already consumed by a prior submit`);
|
|
1001
|
+
throw new RunConfigValidationError(`AgentExecutor.submit: tools[${i}] must be a Tool instance or a builtin tool name`);
|
|
902
1002
|
}
|
|
903
1003
|
const ref = entry.ref;
|
|
904
1004
|
if (ref.kind === "draft") {
|
|
905
1005
|
const bundle = entry._takeDraftBundle();
|
|
906
1006
|
if (!bundle) {
|
|
907
|
-
throw new
|
|
1007
|
+
throw new RunConfigValidationError(`AgentExecutor.submit: tools[${i}] is draft but has no bytes`);
|
|
908
1008
|
}
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
hash: bundle.contentHash,
|
|
912
|
-
contentType: "application/zip"
|
|
913
|
-
});
|
|
914
|
-
refs.push({ ...bundle.ref, assetId: uploaded.assetId });
|
|
1009
|
+
const assetId = await resolveAssetId(entry, bundle, uploader);
|
|
1010
|
+
refs.push({ ...bundle.ref, assetId });
|
|
915
1011
|
continue;
|
|
916
1012
|
}
|
|
917
1013
|
refs.push(ref);
|
|
@@ -924,25 +1020,18 @@ async function prepareAgentsMd(agentsMds, uploader) {
|
|
|
924
1020
|
for (let i = 0; i < agentsMds.length; i++) {
|
|
925
1021
|
const entry = agentsMds[i];
|
|
926
1022
|
if (!(entry instanceof AgentsMd)) {
|
|
927
|
-
throw new
|
|
928
|
-
}
|
|
929
|
-
if (entry.isConsumed) {
|
|
930
|
-
throw new Error(`AgentExecutor.submit: agentsMd[${i}] was already consumed by a prior submit`);
|
|
1023
|
+
throw new RunConfigValidationError(`AgentExecutor.submit: agentsMd[${i}] must be an AgentsMd instance`);
|
|
931
1024
|
}
|
|
932
1025
|
const ref = entry.ref;
|
|
933
1026
|
if (ref.kind === "draft") {
|
|
934
1027
|
const bundle = entry._takeDraftBundle();
|
|
935
1028
|
if (!bundle) {
|
|
936
|
-
throw new
|
|
1029
|
+
throw new RunConfigValidationError(`AgentExecutor.submit: agentsMd[${i}] is draft but has no bytes`);
|
|
937
1030
|
}
|
|
938
|
-
const
|
|
939
|
-
bytes: bundle.bytes,
|
|
940
|
-
hash: bundle.contentHash,
|
|
941
|
-
contentType: "application/zip"
|
|
942
|
-
});
|
|
1031
|
+
const assetId = await resolveAssetId(entry, bundle, uploader);
|
|
943
1032
|
refs.push({
|
|
944
1033
|
kind: "asset",
|
|
945
|
-
assetId
|
|
1034
|
+
assetId,
|
|
946
1035
|
name: bundle.name
|
|
947
1036
|
});
|
|
948
1037
|
continue;
|
|
@@ -957,25 +1046,18 @@ async function prepareFiles(files, uploader) {
|
|
|
957
1046
|
for (let i = 0; i < files.length; i++) {
|
|
958
1047
|
const entry = files[i];
|
|
959
1048
|
if (!(entry instanceof File)) {
|
|
960
|
-
throw new
|
|
961
|
-
}
|
|
962
|
-
if (entry.isConsumed) {
|
|
963
|
-
throw new Error(`AgentExecutor.submit: files[${i}] was already consumed by a prior submit`);
|
|
1049
|
+
throw new RunConfigValidationError(`AgentExecutor.submit: files[${i}] must be a File instance`);
|
|
964
1050
|
}
|
|
965
1051
|
const ref = entry.ref;
|
|
966
1052
|
if (ref.kind === "draft") {
|
|
967
1053
|
const bundle = entry._takeDraftBundle();
|
|
968
1054
|
if (!bundle) {
|
|
969
|
-
throw new
|
|
1055
|
+
throw new RunConfigValidationError(`AgentExecutor.submit: files[${i}] is draft but has no bytes`);
|
|
970
1056
|
}
|
|
971
|
-
const
|
|
972
|
-
bytes: bundle.bytes,
|
|
973
|
-
hash: bundle.contentHash,
|
|
974
|
-
contentType: "application/zip"
|
|
975
|
-
});
|
|
1057
|
+
const assetId = await resolveAssetId(entry, bundle, uploader);
|
|
976
1058
|
refs.push({
|
|
977
1059
|
kind: "asset",
|
|
978
|
-
assetId
|
|
1060
|
+
assetId,
|
|
979
1061
|
name: bundle.name,
|
|
980
1062
|
mountPath: bundle.mountPath
|
|
981
1063
|
});
|
|
@@ -988,7 +1070,7 @@ async function prepareFiles(files, uploader) {
|
|
|
988
1070
|
function getSubmittedRunId(response) {
|
|
989
1071
|
const id = response.id ?? response.runId;
|
|
990
1072
|
if (typeof id !== "string" || id.length === 0) {
|
|
991
|
-
throw new
|
|
1073
|
+
throw new RunStateError("AgentExecutor.submit: submit response did not include a run id");
|
|
992
1074
|
}
|
|
993
1075
|
return id;
|
|
994
1076
|
}
|
|
@@ -1001,14 +1083,14 @@ function mergeMcpServers(inputs, explicitSecrets) {
|
|
|
1001
1083
|
for (let i = 0; i < inputs.length; i++) {
|
|
1002
1084
|
const entry = inputs[i];
|
|
1003
1085
|
if (!(entry instanceof McpServer)) {
|
|
1004
|
-
throw new
|
|
1086
|
+
throw new RunConfigValidationError(`AgentExecutor.submit: mcpServers[${i}] must be an McpServer instance`);
|
|
1005
1087
|
}
|
|
1006
1088
|
submissionMcpServers.push(entry.toSubmissionEntry());
|
|
1007
1089
|
const secret = entry.toSecretEntry();
|
|
1008
1090
|
if (secret) {
|
|
1009
1091
|
const existing = secretByName.get(secret.name);
|
|
1010
1092
|
if (existing && existing.url !== secret.url) {
|
|
1011
|
-
throw new
|
|
1093
|
+
throw new RunConfigValidationError(`AgentExecutor.submit: mcpServers[${i}].url conflicts with secrets.mcpServers["${secret.name}"]`);
|
|
1012
1094
|
}
|
|
1013
1095
|
secretByName.set(secret.name, secret);
|
|
1014
1096
|
}
|
|
@@ -1036,7 +1118,7 @@ function mergeProxyEndpointAuth(fromInstances, fromExplicitSecrets) {
|
|
|
1036
1118
|
for (const entry of fromInstances) {
|
|
1037
1119
|
const existing = byName.get(entry.name);
|
|
1038
1120
|
if (existing && existing.value.type !== entry.value.type) {
|
|
1039
|
-
throw new
|
|
1121
|
+
throw new RunConfigValidationError(`AgentExecutor.submit: proxyEndpoint "${entry.name}" auth type conflicts ` +
|
|
1040
1122
|
`with secrets.proxyEndpointAuth (instance=${entry.value.type}, secrets=${existing.value.type})`);
|
|
1041
1123
|
}
|
|
1042
1124
|
byName.set(entry.name, entry);
|