@runtypelabs/sdk 10.8.1 → 10.9.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/dist/index.cjs +987 -191
- package/dist/index.d.cts +1719 -178
- package/dist/index.d.ts +1719 -178
- package/dist/index.mjs +966 -192
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -2114,7 +2114,7 @@ function asRecord(value) {
|
|
|
2114
2114
|
* Reading the structured fields FIRST is what makes the typed alias errors fire
|
|
2115
2115
|
* for `createClient()` and the CLI, not just for `Runtype.agents`.
|
|
2116
2116
|
*/
|
|
2117
|
-
function parseRequestError$
|
|
2117
|
+
function parseRequestError$7(err) {
|
|
2118
2118
|
if (!(err instanceof Error)) return {
|
|
2119
2119
|
status: null,
|
|
2120
2120
|
body: null
|
|
@@ -2149,7 +2149,7 @@ function asString$1(value, fallback) {
|
|
|
2149
2149
|
* failure exactly as the transport threw it.
|
|
2150
2150
|
*/
|
|
2151
2151
|
function toAliasError(err, agentId, alias) {
|
|
2152
|
-
const { status, body } = parseRequestError$
|
|
2152
|
+
const { status, body } = parseRequestError$7(err);
|
|
2153
2153
|
if (status === null) return null;
|
|
2154
2154
|
const error = body && typeof body.error === "string" ? body.error : void 0;
|
|
2155
2155
|
if (status === 412 && body && typeof body.expected === "number") return new AgentAliasRevisionMismatchError({
|
|
@@ -2339,6 +2339,174 @@ var AgentDeploymentsNamespace = class {
|
|
|
2339
2339
|
}
|
|
2340
2340
|
};
|
|
2341
2341
|
//#endregion
|
|
2342
|
+
//#region src/content-hash.ts
|
|
2343
|
+
/**
|
|
2344
|
+
* Canonical content-hash primitives shared by the `*.ensure()` config-as-code
|
|
2345
|
+
* surfaces (products, tools, skills, surfaces, products-ensure-fpo). Each of
|
|
2346
|
+
* those modules computes a SHA-256 content hash for its hash-first `/ensure`
|
|
2347
|
+
* wire protocol (see docs/adr/0003-agent-config-as-code-ensure.md) — the hash
|
|
2348
|
+
* output for any given definition is a WIRE CONTRACT with the server, so this
|
|
2349
|
+
* module must stay dependency-free (the SDK is published to npm and must not
|
|
2350
|
+
* import `@runtypelabs/shared`, a private package) and must not change its
|
|
2351
|
+
* normalization behavior without a matching server-side change.
|
|
2352
|
+
*
|
|
2353
|
+
* `isPlainObject` and `normalizeValue` here are mirrored from
|
|
2354
|
+
* `packages/shared/src/utils/*-content-hash.ts`. `normalizeValue` sorts object
|
|
2355
|
+
* keys and drops `null`/`undefined` entries recursively — this is the
|
|
2356
|
+
* canonical normalization used by products, tools, skills, surfaces, and the
|
|
2357
|
+
* whole-FPO hash.
|
|
2358
|
+
*
|
|
2359
|
+
* NOT every `*-ensure.ts` module uses this normalization: `flows-ensure.ts`
|
|
2360
|
+
* (`normalizeConfigForHash`/`normalizeStepForHash`) and `evals-ensure.ts`
|
|
2361
|
+
* (`normalizeForHash`) keep `null` values (they drop only `undefined`) and,
|
|
2362
|
+
* in the flow case, use a shallower array-recursion rule. Those are real
|
|
2363
|
+
* semantic divergences from this canonical normalizer, not incidental
|
|
2364
|
+
* duplication — changing either to reuse `normalizeValue` would alter the
|
|
2365
|
+
* content hash for definitions containing explicit `null`s and break the
|
|
2366
|
+
* wire contract with already-converged flows/evals. They stay local by
|
|
2367
|
+
* design; see the divergence comment at each site.
|
|
2368
|
+
*/
|
|
2369
|
+
function isPlainObject$1(value) {
|
|
2370
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2371
|
+
}
|
|
2372
|
+
/**
|
|
2373
|
+
* Recursively normalizes a value for canonical hashing: object keys are
|
|
2374
|
+
* sorted, and `null`/`undefined` entries are dropped. Arrays preserve order
|
|
2375
|
+
* and have each item normalized in turn.
|
|
2376
|
+
*/
|
|
2377
|
+
function normalizeValue$1(value) {
|
|
2378
|
+
if (Array.isArray(value)) return value.map((item) => normalizeValue$1(item));
|
|
2379
|
+
if (isPlainObject$1(value)) {
|
|
2380
|
+
const normalized = {};
|
|
2381
|
+
for (const key of Object.keys(value).sort()) {
|
|
2382
|
+
const entry = value[key];
|
|
2383
|
+
if (entry === void 0 || entry === null) continue;
|
|
2384
|
+
normalized[key] = normalizeValue$1(entry);
|
|
2385
|
+
}
|
|
2386
|
+
return normalized;
|
|
2387
|
+
}
|
|
2388
|
+
return value;
|
|
2389
|
+
}
|
|
2390
|
+
/** SHA-256 (hex) over an already-serialized (typically `JSON.stringify`'d) string. */
|
|
2391
|
+
async function sha256Hex(serialized) {
|
|
2392
|
+
const encoded = new TextEncoder().encode(serialized);
|
|
2393
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
|
|
2394
|
+
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2395
|
+
}
|
|
2396
|
+
//#endregion
|
|
2397
|
+
//#region src/collections-ensure.ts
|
|
2398
|
+
/** Canonical normalized form of a collection definition (slug excluded — identity). */
|
|
2399
|
+
function normalizeRecordCollectionDefinition(definition) {
|
|
2400
|
+
return {
|
|
2401
|
+
displayName: definition.displayName,
|
|
2402
|
+
...definition.description ? { description: definition.description } : {},
|
|
2403
|
+
...definition.icon ? { icon: definition.icon } : {},
|
|
2404
|
+
...isPlainObject$1(definition.schema) ? { schema: normalizeValue$1(definition.schema) } : {},
|
|
2405
|
+
validationMode: definition.validationMode || "off"
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
/** SHA-256 (hex) over the canonical normalized collection definition. */
|
|
2409
|
+
async function computeRecordCollectionContentHash(definition) {
|
|
2410
|
+
return sha256Hex(JSON.stringify(normalizeRecordCollectionDefinition(definition)));
|
|
2411
|
+
}
|
|
2412
|
+
/** A 409 from the ensure protocol (external_modification or remote_changed). */
|
|
2413
|
+
var CollectionEnsureConflictError = class extends Error {
|
|
2414
|
+
constructor(body) {
|
|
2415
|
+
super(body.error ?? `Collection ensure conflict: ${body.code}`);
|
|
2416
|
+
this.name = "CollectionEnsureConflictError";
|
|
2417
|
+
this.code = body.code;
|
|
2418
|
+
this.lastModifiedSource = body.lastModifiedSource;
|
|
2419
|
+
this.modifiedAt = body.modifiedAt;
|
|
2420
|
+
this.currentHash = body.currentHash;
|
|
2421
|
+
}
|
|
2422
|
+
};
|
|
2423
|
+
/** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
|
|
2424
|
+
var CollectionDriftError = class extends Error {
|
|
2425
|
+
constructor(slug, plan) {
|
|
2426
|
+
super(`Collection "${slug}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.collections.pull(slug) to absorb the remote edit into your repo, or re-run ensure to converge.`);
|
|
2427
|
+
this.name = "CollectionDriftError";
|
|
2428
|
+
this.plan = plan;
|
|
2429
|
+
}
|
|
2430
|
+
};
|
|
2431
|
+
function toConflictError$8(err) {
|
|
2432
|
+
if (!(err instanceof Error)) return null;
|
|
2433
|
+
const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
|
|
2434
|
+
if (!match || match[1] !== "409") return null;
|
|
2435
|
+
let body;
|
|
2436
|
+
try {
|
|
2437
|
+
body = JSON.parse(match[2]);
|
|
2438
|
+
} catch {
|
|
2439
|
+
return null;
|
|
2440
|
+
}
|
|
2441
|
+
if (!isPlainObject$1(body)) return null;
|
|
2442
|
+
if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
|
|
2443
|
+
return new CollectionEnsureConflictError(body);
|
|
2444
|
+
}
|
|
2445
|
+
/** Per-transport memo of `{ slug + localHash → server contentHash }`, as in tools.ensure. */
|
|
2446
|
+
const serverHashMemo$9 = /* @__PURE__ */ new WeakMap();
|
|
2447
|
+
function memoFor$9(transport) {
|
|
2448
|
+
let memo = serverHashMemo$9.get(transport);
|
|
2449
|
+
if (!memo) {
|
|
2450
|
+
memo = /* @__PURE__ */ new Map();
|
|
2451
|
+
serverHashMemo$9.set(transport, memo);
|
|
2452
|
+
}
|
|
2453
|
+
return memo;
|
|
2454
|
+
}
|
|
2455
|
+
async function request$7(transport, body) {
|
|
2456
|
+
try {
|
|
2457
|
+
return await transport.post("/collections/ensure", body);
|
|
2458
|
+
} catch (err) {
|
|
2459
|
+
throw toConflictError$8(err) ?? err;
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
/**
|
|
2463
|
+
* Idempotently converge a collection definition. Probes with a content hash and
|
|
2464
|
+
* only ships the full definition when the server reports a miss. Never deletes
|
|
2465
|
+
* a collection and never touches records.
|
|
2466
|
+
*/
|
|
2467
|
+
async function ensureCollection(transport, definition, options = {}) {
|
|
2468
|
+
const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
|
|
2469
|
+
const passthrough = {
|
|
2470
|
+
...onConflict ? { onConflict } : {},
|
|
2471
|
+
...expectedRemoteHash ? { expectedRemoteHash } : {}
|
|
2472
|
+
};
|
|
2473
|
+
if (dryRun || expectNoChanges) {
|
|
2474
|
+
const plan = await request$7(transport, {
|
|
2475
|
+
slug: definition.slug,
|
|
2476
|
+
definition,
|
|
2477
|
+
dryRun: true,
|
|
2478
|
+
...passthrough
|
|
2479
|
+
});
|
|
2480
|
+
if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
|
|
2481
|
+
if (expectNoChanges && plan.changes !== "none") throw new CollectionDriftError(definition.slug, plan);
|
|
2482
|
+
return plan;
|
|
2483
|
+
}
|
|
2484
|
+
const memo = memoFor$9(transport);
|
|
2485
|
+
const localHash = await computeRecordCollectionContentHash(definition);
|
|
2486
|
+
const memoKey = `${definition.slug} ${localHash}`;
|
|
2487
|
+
const probe = await request$7(transport, {
|
|
2488
|
+
slug: definition.slug,
|
|
2489
|
+
contentHash: memo.get(memoKey) ?? localHash,
|
|
2490
|
+
...passthrough
|
|
2491
|
+
});
|
|
2492
|
+
if (probe.result !== "definitionRequired") {
|
|
2493
|
+
if (probe.result !== "plan") memo.set(memoKey, probe.contentHash);
|
|
2494
|
+
return probe;
|
|
2495
|
+
}
|
|
2496
|
+
const converged = await request$7(transport, {
|
|
2497
|
+
slug: definition.slug,
|
|
2498
|
+
definition,
|
|
2499
|
+
...passthrough
|
|
2500
|
+
});
|
|
2501
|
+
if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
|
|
2502
|
+
if (converged.result !== "plan") memo.set(memoKey, converged.contentHash);
|
|
2503
|
+
return converged;
|
|
2504
|
+
}
|
|
2505
|
+
/** Pull the canonical definition and provenance for a collection by slug. */
|
|
2506
|
+
async function pullCollection(transport, slug) {
|
|
2507
|
+
return transport.get(`/collections/${encodeURIComponent(slug)}/pull`);
|
|
2508
|
+
}
|
|
2509
|
+
//#endregion
|
|
2342
2510
|
//#region src/generated-tool-gate.ts
|
|
2343
2511
|
const TOOL_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]{1,63}$/;
|
|
2344
2512
|
const DEFAULT_MAX_CODE_LENGTH = 12e3;
|
|
@@ -2667,6 +2835,239 @@ function normalizeDispatchRequest(request) {
|
|
|
2667
2835
|
};
|
|
2668
2836
|
}
|
|
2669
2837
|
//#endregion
|
|
2838
|
+
//#region src/schedules-ensure.ts
|
|
2839
|
+
function nonEmptyObject(value) {
|
|
2840
|
+
if (!isPlainObject$1(value)) return void 0;
|
|
2841
|
+
const normalized = normalizeValue$1(value);
|
|
2842
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
2843
|
+
}
|
|
2844
|
+
function nonEmptyString(value) {
|
|
2845
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
2846
|
+
}
|
|
2847
|
+
function canonicalRunAt(value) {
|
|
2848
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
2849
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
2850
|
+
return Number.isNaN(date.getTime()) ? String(value) : date.toISOString();
|
|
2851
|
+
}
|
|
2852
|
+
/** Canonical normalized schedule content (name and target excluded — identity). */
|
|
2853
|
+
function normalizeScheduleContent(definition) {
|
|
2854
|
+
const cron = nonEmptyString(definition.cron);
|
|
2855
|
+
const timezone = nonEmptyString(definition.timezone);
|
|
2856
|
+
const runAt = canonicalRunAt(definition.runAt);
|
|
2857
|
+
const recordIds = Array.isArray(definition.recordIds) && definition.recordIds.length > 0 ? [...definition.recordIds] : void 0;
|
|
2858
|
+
const recordType = nonEmptyString(definition.recordType);
|
|
2859
|
+
const recordFilter = nonEmptyObject(definition.recordFilter);
|
|
2860
|
+
const messages = Array.isArray(definition.messages) && definition.messages.length > 0 ? normalizeValue$1(definition.messages) : void 0;
|
|
2861
|
+
const inputs = nonEmptyObject(definition.inputs);
|
|
2862
|
+
const executionOptions = nonEmptyObject(definition.executionOptions);
|
|
2863
|
+
return {
|
|
2864
|
+
triggerType: definition.triggerType === "one_time" ? "one_time" : "recurring",
|
|
2865
|
+
...cron !== void 0 ? { cron } : {},
|
|
2866
|
+
...timezone !== void 0 ? { timezone } : {},
|
|
2867
|
+
...runAt !== void 0 ? { runAt } : {},
|
|
2868
|
+
...recordIds !== void 0 ? { recordIds } : {},
|
|
2869
|
+
...recordType !== void 0 ? { recordType } : {},
|
|
2870
|
+
...recordFilter !== void 0 ? { recordFilter } : {},
|
|
2871
|
+
...messages !== void 0 ? { messages } : {},
|
|
2872
|
+
...inputs !== void 0 ? { inputs } : {},
|
|
2873
|
+
...executionOptions !== void 0 ? { executionOptions } : {},
|
|
2874
|
+
...typeof definition.enabled === "boolean" ? { enabled: definition.enabled } : {}
|
|
2875
|
+
};
|
|
2876
|
+
}
|
|
2877
|
+
/** SHA-256 (hex) over canonical schedule content, in the flat content shape. */
|
|
2878
|
+
async function computeScheduleContentHash(definition) {
|
|
2879
|
+
return sha256Hex(JSON.stringify(normalizeScheduleContent(definition)));
|
|
2880
|
+
}
|
|
2881
|
+
/** The content hash of a `defineSchedule` definition. */
|
|
2882
|
+
async function computeScheduleDefinitionHash(definition) {
|
|
2883
|
+
return computeScheduleContentHash({
|
|
2884
|
+
triggerType: definition.trigger.type,
|
|
2885
|
+
cron: definition.trigger.cron,
|
|
2886
|
+
timezone: definition.trigger.timezone,
|
|
2887
|
+
runAt: definition.trigger.runAt,
|
|
2888
|
+
recordIds: definition.recordIds,
|
|
2889
|
+
recordType: definition.recordType,
|
|
2890
|
+
recordFilter: definition.recordFilter,
|
|
2891
|
+
messages: definition.messages,
|
|
2892
|
+
inputs: definition.inputs,
|
|
2893
|
+
executionOptions: definition.executionOptions,
|
|
2894
|
+
enabled: definition.enabled
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
const TARGET_KEYS = [
|
|
2898
|
+
"flowId",
|
|
2899
|
+
"agentId",
|
|
2900
|
+
"flowName",
|
|
2901
|
+
"agentName"
|
|
2902
|
+
];
|
|
2903
|
+
const DEFINE_SCHEDULE_KEYS = /* @__PURE__ */ new Set([
|
|
2904
|
+
"name",
|
|
2905
|
+
"target",
|
|
2906
|
+
"trigger",
|
|
2907
|
+
"recordIds",
|
|
2908
|
+
"recordType",
|
|
2909
|
+
"recordFilter",
|
|
2910
|
+
"messages",
|
|
2911
|
+
"inputs",
|
|
2912
|
+
"executionOptions",
|
|
2913
|
+
"enabled"
|
|
2914
|
+
]);
|
|
2915
|
+
/**
|
|
2916
|
+
* Pure-local declarative constructor for a standalone schedule. No I/O.
|
|
2917
|
+
* Validates structure; cron, timezone, record filter and plan limits are
|
|
2918
|
+
* checked server-side at ensure time.
|
|
2919
|
+
*
|
|
2920
|
+
* @example
|
|
2921
|
+
* ```typescript
|
|
2922
|
+
* const nightly = defineSchedule({
|
|
2923
|
+
* name: 'Nightly digest',
|
|
2924
|
+
* target: { flowName: 'Digest' },
|
|
2925
|
+
* trigger: { type: 'recurring', cron: '0 2 * * *', timezone: 'UTC' },
|
|
2926
|
+
* })
|
|
2927
|
+
* ```
|
|
2928
|
+
*/
|
|
2929
|
+
function defineSchedule(input) {
|
|
2930
|
+
if (!input || typeof input !== "object") throw new Error("defineSchedule requires a definition object");
|
|
2931
|
+
if (typeof input.name !== "string" || input.name.length === 0) throw new Error("defineSchedule requires a non-empty string \"name\"");
|
|
2932
|
+
if (!isPlainObject$1(input.target)) throw new Error("defineSchedule requires a \"target\" object");
|
|
2933
|
+
const target = input.target;
|
|
2934
|
+
const setKeys = TARGET_KEYS.filter((key) => target[key] !== void 0);
|
|
2935
|
+
const extraKeys = Object.keys(target).filter((key) => !TARGET_KEYS.includes(key));
|
|
2936
|
+
if (setKeys.length !== 1 || extraKeys.length > 0) throw new Error("defineSchedule \"target\" needs exactly one of flowId, agentId, flowName, agentName");
|
|
2937
|
+
const trigger = input.trigger;
|
|
2938
|
+
if (!isPlainObject$1(trigger) || trigger.type !== "recurring" && trigger.type !== "one_time") throw new Error("defineSchedule requires \"trigger.type\" to be \"recurring\" or \"one_time\"");
|
|
2939
|
+
if (trigger.type === "recurring" && (!trigger.cron || !trigger.timezone)) throw new Error("defineSchedule: recurring triggers require \"cron\" and \"timezone\"");
|
|
2940
|
+
if (trigger.type === "one_time" && !trigger.runAt) throw new Error("defineSchedule: one_time triggers require \"runAt\"");
|
|
2941
|
+
const unknownKeys = Object.keys(input).filter((key) => !DEFINE_SCHEDULE_KEYS.has(key));
|
|
2942
|
+
if (unknownKeys.length > 0) throw new Error(`defineSchedule: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are ${[...DEFINE_SCHEDULE_KEYS].join(", ")}.`);
|
|
2943
|
+
return { ...input };
|
|
2944
|
+
}
|
|
2945
|
+
/** A 409 from the ensure protocol (external_modification or remote_changed). */
|
|
2946
|
+
var ScheduleEnsureConflictError = class extends Error {
|
|
2947
|
+
constructor(body) {
|
|
2948
|
+
super(body.error ?? `Schedule ensure conflict: ${body.code}`);
|
|
2949
|
+
this.name = "ScheduleEnsureConflictError";
|
|
2950
|
+
this.code = body.code;
|
|
2951
|
+
this.lastModifiedSource = body.lastModifiedSource;
|
|
2952
|
+
this.modifiedAt = body.modifiedAt;
|
|
2953
|
+
this.currentHash = body.currentHash;
|
|
2954
|
+
}
|
|
2955
|
+
};
|
|
2956
|
+
/** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
|
|
2957
|
+
var ScheduleDriftError = class extends Error {
|
|
2958
|
+
constructor(plan) {
|
|
2959
|
+
super(`Schedule "${plan.scheduleId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run schedules.pull(name, target) to absorb the remote edit into your repo, or re-run ensure to converge.`);
|
|
2960
|
+
this.name = "ScheduleDriftError";
|
|
2961
|
+
this.plan = plan;
|
|
2962
|
+
}
|
|
2963
|
+
};
|
|
2964
|
+
function errorStatusAndBody(err) {
|
|
2965
|
+
if (!(err instanceof Error)) return {
|
|
2966
|
+
status: null,
|
|
2967
|
+
body: null
|
|
2968
|
+
};
|
|
2969
|
+
const typed = err;
|
|
2970
|
+
if (typeof typed.statusCode === "number") return {
|
|
2971
|
+
status: typed.statusCode,
|
|
2972
|
+
body: typed.data
|
|
2973
|
+
};
|
|
2974
|
+
const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
|
|
2975
|
+
if (!match) return {
|
|
2976
|
+
status: null,
|
|
2977
|
+
body: null
|
|
2978
|
+
};
|
|
2979
|
+
try {
|
|
2980
|
+
return {
|
|
2981
|
+
status: Number(match[1]),
|
|
2982
|
+
body: JSON.parse(match[2])
|
|
2983
|
+
};
|
|
2984
|
+
} catch {
|
|
2985
|
+
return {
|
|
2986
|
+
status: Number(match[1]),
|
|
2987
|
+
body: null
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
function toConflictError$7(err) {
|
|
2992
|
+
const { status, body } = errorStatusAndBody(err);
|
|
2993
|
+
if (status !== 409 || !isPlainObject$1(body)) return null;
|
|
2994
|
+
if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
|
|
2995
|
+
return new ScheduleEnsureConflictError(body);
|
|
2996
|
+
}
|
|
2997
|
+
const serverHashMemo$8 = /* @__PURE__ */ new WeakMap();
|
|
2998
|
+
function memoFor$8(client) {
|
|
2999
|
+
let memo = serverHashMemo$8.get(client);
|
|
3000
|
+
if (!memo) {
|
|
3001
|
+
memo = /* @__PURE__ */ new Map();
|
|
3002
|
+
serverHashMemo$8.set(client, memo);
|
|
3003
|
+
}
|
|
3004
|
+
return memo;
|
|
3005
|
+
}
|
|
3006
|
+
async function request$6(client, body) {
|
|
3007
|
+
try {
|
|
3008
|
+
return await client.post("/schedules/ensure", body);
|
|
3009
|
+
} catch (err) {
|
|
3010
|
+
const conflict = toConflictError$7(err);
|
|
3011
|
+
if (conflict) throw conflict;
|
|
3012
|
+
throw err;
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
/**
|
|
3016
|
+
* Idempotently converge a standalone schedule. Hash-first: probes with a
|
|
3017
|
+
* content hash and ships the full definition only on a miss. Never deletes.
|
|
3018
|
+
*/
|
|
3019
|
+
async function ensureSchedule(client, definition, options = {}) {
|
|
3020
|
+
const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
|
|
3021
|
+
const identity = {
|
|
3022
|
+
name: definition.name,
|
|
3023
|
+
target: definition.target
|
|
3024
|
+
};
|
|
3025
|
+
const passthrough = {
|
|
3026
|
+
...onConflict ? { onConflict } : {},
|
|
3027
|
+
...expectedRemoteHash ? { expectedRemoteHash } : {}
|
|
3028
|
+
};
|
|
3029
|
+
if (dryRun || expectNoChanges) {
|
|
3030
|
+
const plan = await request$6(client, {
|
|
3031
|
+
...identity,
|
|
3032
|
+
definition,
|
|
3033
|
+
dryRun: true,
|
|
3034
|
+
...passthrough
|
|
3035
|
+
});
|
|
3036
|
+
if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
|
|
3037
|
+
if (expectNoChanges && plan.changes !== "none") throw new ScheduleDriftError(plan);
|
|
3038
|
+
return plan;
|
|
3039
|
+
}
|
|
3040
|
+
const memo = memoFor$8(client);
|
|
3041
|
+
const localHash = await computeScheduleDefinitionHash(definition);
|
|
3042
|
+
const memoKey = `${JSON.stringify(identity)} ${localHash}`;
|
|
3043
|
+
if (!expectedRemoteHash) {
|
|
3044
|
+
const probe = await request$6(client, {
|
|
3045
|
+
...identity,
|
|
3046
|
+
contentHash: memo.get(memoKey) ?? localHash,
|
|
3047
|
+
...passthrough
|
|
3048
|
+
});
|
|
3049
|
+
if (probe.result !== "definitionRequired") {
|
|
3050
|
+
if (probe.result !== "plan") memo.set(memoKey, probe.contentHash);
|
|
3051
|
+
return probe;
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
const converged = await request$6(client, {
|
|
3055
|
+
...identity,
|
|
3056
|
+
definition,
|
|
3057
|
+
...passthrough
|
|
3058
|
+
});
|
|
3059
|
+
if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
|
|
3060
|
+
if (converged.result !== "plan") memo.set(memoKey, converged.contentHash);
|
|
3061
|
+
return converged;
|
|
3062
|
+
}
|
|
3063
|
+
/** Pull the canonical definition + provenance for a schedule by name and target. */
|
|
3064
|
+
async function pullSchedule(client, name, target) {
|
|
3065
|
+
return client.get("/schedules/pull", {
|
|
3066
|
+
name,
|
|
3067
|
+
...target
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3070
|
+
//#endregion
|
|
2670
3071
|
//#region src/offload-markers.ts
|
|
2671
3072
|
const LEDGER_ARTIFACT_LINE_PREFIX = "Ledger artifact: ";
|
|
2672
3073
|
const SPILL_MARKER_PATTERN = /\[Output saved to (art_[A-Za-z0-9_-]+) \(([\d,]+) chars?\); read_offloaded_output to view\]/;
|
|
@@ -4640,6 +5041,28 @@ var CollectionsEndpoint = class {
|
|
|
4640
5041
|
async typegen() {
|
|
4641
5042
|
return this.client.get("/collections/types.d.ts");
|
|
4642
5043
|
}
|
|
5044
|
+
/**
|
|
5045
|
+
* Idempotently converge a repo-declared collection (display fields, schema,
|
|
5046
|
+
* validationMode) by slug. Hash-first; `dryRun` / `expectNoChanges` plan
|
|
5047
|
+
* without writing. Never deletes a collection or touches records.
|
|
5048
|
+
*
|
|
5049
|
+
* @example
|
|
5050
|
+
* ```typescript
|
|
5051
|
+
* await client.collections.ensure({
|
|
5052
|
+
* slug: 'customers',
|
|
5053
|
+
* displayName: 'Customers',
|
|
5054
|
+
* schema: { type: 'object', properties: { status: { type: 'string' } } },
|
|
5055
|
+
* validationMode: 'warn',
|
|
5056
|
+
* })
|
|
5057
|
+
* ```
|
|
5058
|
+
*/
|
|
5059
|
+
async ensure(definition, options) {
|
|
5060
|
+
return ensureCollection(this.client, definition, options);
|
|
5061
|
+
}
|
|
5062
|
+
/** Pull the canonical definition and provenance for a collection — the absorb-drift direction. */
|
|
5063
|
+
async pull(slug) {
|
|
5064
|
+
return pullCollection(this.client, slug);
|
|
5065
|
+
}
|
|
4643
5066
|
};
|
|
4644
5067
|
/**
|
|
4645
5068
|
* Agent API key REQUESTS — a device-authorization-style grant.
|
|
@@ -8172,6 +8595,21 @@ var SchedulesEndpoint = class {
|
|
|
8172
8595
|
return this.client.post("/schedules", data);
|
|
8173
8596
|
}
|
|
8174
8597
|
/**
|
|
8598
|
+
* Idempotently converge a `defineSchedule` definition on a standalone flow or
|
|
8599
|
+
* agent. Identity is name + target; the steady state is one hash probe. Never
|
|
8600
|
+
* deletes, and an omitted `enabled` leaves a paused schedule paused.
|
|
8601
|
+
*/
|
|
8602
|
+
async ensure(definition, options = {}) {
|
|
8603
|
+
return ensureSchedule(this.client, definition, options);
|
|
8604
|
+
}
|
|
8605
|
+
/**
|
|
8606
|
+
* Pull the canonical definition + provenance for a schedule by name and
|
|
8607
|
+
* target — the absorb-drift direction of `ensure`.
|
|
8608
|
+
*/
|
|
8609
|
+
async pull(name, target) {
|
|
8610
|
+
return pullSchedule(this.client, name, target);
|
|
8611
|
+
}
|
|
8612
|
+
/**
|
|
8175
8613
|
* Update a schedule (partial update supported).
|
|
8176
8614
|
*/
|
|
8177
8615
|
async update(id, data) {
|
|
@@ -8667,61 +9105,6 @@ async function followDetachedResume(client, ack, after, carried = {}) {
|
|
|
8667
9105
|
};
|
|
8668
9106
|
}
|
|
8669
9107
|
//#endregion
|
|
8670
|
-
//#region src/content-hash.ts
|
|
8671
|
-
/**
|
|
8672
|
-
* Canonical content-hash primitives shared by the `*.ensure()` config-as-code
|
|
8673
|
-
* surfaces (products, tools, skills, surfaces, products-ensure-fpo). Each of
|
|
8674
|
-
* those modules computes a SHA-256 content hash for its hash-first `/ensure`
|
|
8675
|
-
* wire protocol (see docs/adr/0003-agent-config-as-code-ensure.md) — the hash
|
|
8676
|
-
* output for any given definition is a WIRE CONTRACT with the server, so this
|
|
8677
|
-
* module must stay dependency-free (the SDK is published to npm and must not
|
|
8678
|
-
* import `@runtypelabs/shared`, a private package) and must not change its
|
|
8679
|
-
* normalization behavior without a matching server-side change.
|
|
8680
|
-
*
|
|
8681
|
-
* `isPlainObject` and `normalizeValue` here are mirrored from
|
|
8682
|
-
* `packages/shared/src/utils/*-content-hash.ts`. `normalizeValue` sorts object
|
|
8683
|
-
* keys and drops `null`/`undefined` entries recursively — this is the
|
|
8684
|
-
* canonical normalization used by products, tools, skills, surfaces, and the
|
|
8685
|
-
* whole-FPO hash.
|
|
8686
|
-
*
|
|
8687
|
-
* NOT every `*-ensure.ts` module uses this normalization: `flows-ensure.ts`
|
|
8688
|
-
* (`normalizeConfigForHash`/`normalizeStepForHash`) and `evals-ensure.ts`
|
|
8689
|
-
* (`normalizeForHash`) keep `null` values (they drop only `undefined`) and,
|
|
8690
|
-
* in the flow case, use a shallower array-recursion rule. Those are real
|
|
8691
|
-
* semantic divergences from this canonical normalizer, not incidental
|
|
8692
|
-
* duplication — changing either to reuse `normalizeValue` would alter the
|
|
8693
|
-
* content hash for definitions containing explicit `null`s and break the
|
|
8694
|
-
* wire contract with already-converged flows/evals. They stay local by
|
|
8695
|
-
* design; see the divergence comment at each site.
|
|
8696
|
-
*/
|
|
8697
|
-
function isPlainObject$1(value) {
|
|
8698
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8699
|
-
}
|
|
8700
|
-
/**
|
|
8701
|
-
* Recursively normalizes a value for canonical hashing: object keys are
|
|
8702
|
-
* sorted, and `null`/`undefined` entries are dropped. Arrays preserve order
|
|
8703
|
-
* and have each item normalized in turn.
|
|
8704
|
-
*/
|
|
8705
|
-
function normalizeValue$1(value) {
|
|
8706
|
-
if (Array.isArray(value)) return value.map((item) => normalizeValue$1(item));
|
|
8707
|
-
if (isPlainObject$1(value)) {
|
|
8708
|
-
const normalized = {};
|
|
8709
|
-
for (const key of Object.keys(value).sort()) {
|
|
8710
|
-
const entry = value[key];
|
|
8711
|
-
if (entry === void 0 || entry === null) continue;
|
|
8712
|
-
normalized[key] = normalizeValue$1(entry);
|
|
8713
|
-
}
|
|
8714
|
-
return normalized;
|
|
8715
|
-
}
|
|
8716
|
-
return value;
|
|
8717
|
-
}
|
|
8718
|
-
/** SHA-256 (hex) over an already-serialized (typically `JSON.stringify`'d) string. */
|
|
8719
|
-
async function sha256Hex(serialized) {
|
|
8720
|
-
const encoded = new TextEncoder().encode(serialized);
|
|
8721
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
|
|
8722
|
-
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
8723
|
-
}
|
|
8724
|
-
//#endregion
|
|
8725
9108
|
//#region src/evals-ensure.ts
|
|
8726
9109
|
const CHECK_GRADER_KINDS = /* @__PURE__ */ new Set([
|
|
8727
9110
|
"contains",
|
|
@@ -9115,6 +9498,25 @@ async function computeEvalContentHash(definition) {
|
|
|
9115
9498
|
};
|
|
9116
9499
|
return sha256Hex(JSON.stringify(canonical));
|
|
9117
9500
|
}
|
|
9501
|
+
/** A 409 from the eval ensure protocol (external_modification or remote_changed). */
|
|
9502
|
+
var EvalEnsureConflictError = class extends Error {
|
|
9503
|
+
constructor(body) {
|
|
9504
|
+
super(body.error ?? `Eval ensure conflict: ${body.code}`);
|
|
9505
|
+
this.name = "EvalEnsureConflictError";
|
|
9506
|
+
this.code = body.code;
|
|
9507
|
+
this.lastModifiedSource = body.lastModifiedSource;
|
|
9508
|
+
this.modifiedAt = body.modifiedAt;
|
|
9509
|
+
this.currentHash = body.currentHash;
|
|
9510
|
+
}
|
|
9511
|
+
};
|
|
9512
|
+
/** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
|
|
9513
|
+
var EvalDriftError = class extends Error {
|
|
9514
|
+
constructor(plan) {
|
|
9515
|
+
super(`Eval suite "${plan.suiteId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.evals.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`);
|
|
9516
|
+
this.name = "EvalDriftError";
|
|
9517
|
+
this.plan = plan;
|
|
9518
|
+
}
|
|
9519
|
+
};
|
|
9118
9520
|
/**
|
|
9119
9521
|
* Per-client memo of `{ name + localHash → server contentHash }` so a hot
|
|
9120
9522
|
* process pays the full-definition round trip once per boot (steady state
|
|
@@ -9122,44 +9524,75 @@ async function computeEvalContentHash(definition) {
|
|
|
9122
9524
|
* hash, so a definition that changes within the process can never false-match a
|
|
9123
9525
|
* stale server hash. Mirrors the flow ensure memo.
|
|
9124
9526
|
*/
|
|
9125
|
-
const serverHashMemo$
|
|
9126
|
-
function memoFor$
|
|
9127
|
-
let memo = serverHashMemo$
|
|
9527
|
+
const serverHashMemo$7 = /* @__PURE__ */ new WeakMap();
|
|
9528
|
+
function memoFor$7(client) {
|
|
9529
|
+
let memo = serverHashMemo$7.get(client);
|
|
9128
9530
|
if (!memo) {
|
|
9129
9531
|
memo = /* @__PURE__ */ new Map();
|
|
9130
|
-
serverHashMemo$
|
|
9532
|
+
serverHashMemo$7.set(client, memo);
|
|
9131
9533
|
}
|
|
9132
9534
|
return memo;
|
|
9133
9535
|
}
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
9536
|
+
function toEvalConflictError(err) {
|
|
9537
|
+
if (!(err instanceof Error)) return null;
|
|
9538
|
+
const match = err.message.match(/^API request failed: 409 .*? - ([\s\S]*)$/);
|
|
9539
|
+
if (!match) return null;
|
|
9540
|
+
let body;
|
|
9541
|
+
try {
|
|
9542
|
+
body = JSON.parse(match[1]);
|
|
9543
|
+
} catch {
|
|
9544
|
+
return null;
|
|
9545
|
+
}
|
|
9546
|
+
if (!isPlainObject$1(body)) return null;
|
|
9547
|
+
if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
|
|
9548
|
+
return new EvalEnsureConflictError(body);
|
|
9549
|
+
}
|
|
9550
|
+
async function postEvalEnsure(client, body) {
|
|
9551
|
+
try {
|
|
9552
|
+
return await client.post("/eval/ensure", body);
|
|
9553
|
+
} catch (err) {
|
|
9554
|
+
throw toEvalConflictError(err) ?? err;
|
|
9555
|
+
}
|
|
9556
|
+
}
|
|
9557
|
+
async function ensureEval(client, definition, options = {}) {
|
|
9145
9558
|
if (definition.virtual) throw new Error("Cannot ensure a virtual eval: virtual evals are ephemeral (nothing is persisted to converge). Remove `virtual: true` to converge a durable suite, or run it directly.");
|
|
9146
|
-
const
|
|
9559
|
+
const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
|
|
9560
|
+
const passthrough = {
|
|
9561
|
+
...onConflict ? { onConflict } : {},
|
|
9562
|
+
...expectedRemoteHash ? { expectedRemoteHash } : {}
|
|
9563
|
+
};
|
|
9564
|
+
if (dryRun || expectNoChanges) {
|
|
9565
|
+
const plan = await postEvalEnsure(client, {
|
|
9566
|
+
name: definition.name,
|
|
9567
|
+
definition,
|
|
9568
|
+
dryRun: true,
|
|
9569
|
+
...passthrough
|
|
9570
|
+
});
|
|
9571
|
+
if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
|
|
9572
|
+
if (expectNoChanges && plan.changes !== "none") throw new EvalDriftError(plan);
|
|
9573
|
+
return plan;
|
|
9574
|
+
}
|
|
9575
|
+
const memo = memoFor$7(client);
|
|
9147
9576
|
const localHash = await computeEvalContentHash(definition);
|
|
9148
9577
|
const memoKey = `${definition.name} ${localHash}`;
|
|
9149
9578
|
const contentHash = memo.get(memoKey) ?? localHash;
|
|
9150
|
-
const probe = await client
|
|
9579
|
+
const probe = await postEvalEnsure(client, {
|
|
9151
9580
|
name: definition.name,
|
|
9152
|
-
contentHash
|
|
9581
|
+
contentHash,
|
|
9582
|
+
...passthrough
|
|
9153
9583
|
});
|
|
9584
|
+
if (probe.result === "plan") throw new Error("Server returned a plan for a non-dryRun request");
|
|
9154
9585
|
if (probe.result !== "definitionRequired") {
|
|
9155
9586
|
memo.set(memoKey, probe.contentHash);
|
|
9156
9587
|
return probe;
|
|
9157
9588
|
}
|
|
9158
|
-
const converged = await client
|
|
9589
|
+
const converged = await postEvalEnsure(client, {
|
|
9159
9590
|
name: definition.name,
|
|
9160
|
-
definition
|
|
9591
|
+
definition,
|
|
9592
|
+
...passthrough
|
|
9161
9593
|
});
|
|
9162
9594
|
if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
|
|
9595
|
+
if (converged.result === "plan") throw new Error("Server returned a plan for a non-dryRun request");
|
|
9163
9596
|
memo.set(memoKey, converged.contentHash);
|
|
9164
9597
|
return converged;
|
|
9165
9598
|
}
|
|
@@ -9214,9 +9647,11 @@ function normalizeStepForHash(step) {
|
|
|
9214
9647
|
}
|
|
9215
9648
|
/** SHA-256 (hex) over the canonical normalized step list (steps only). */
|
|
9216
9649
|
async function computeFlowContentHash(steps) {
|
|
9217
|
-
const normalized =
|
|
9218
|
-
|
|
9219
|
-
|
|
9650
|
+
const normalized = steps.map((step) => isPlainObject$1(step) ? step : {}).map((step, index) => ({
|
|
9651
|
+
...step,
|
|
9652
|
+
name: typeof step.name === "string" ? step.name : `Step ${index + 1}`,
|
|
9653
|
+
order: typeof step.order === "number" ? step.order : index + 1
|
|
9654
|
+
})).sort((a, b) => a.order - b.order).map(normalizeStepForHash);
|
|
9220
9655
|
return sha256Hex(JSON.stringify(normalized));
|
|
9221
9656
|
}
|
|
9222
9657
|
const DEFINE_FLOW_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -9235,8 +9670,8 @@ const DEFINE_FLOW_STEP_KEYS = /* @__PURE__ */ new Set([
|
|
|
9235
9670
|
/**
|
|
9236
9671
|
* Collect account-scoped `tool_…` references from a step config (inlined copy
|
|
9237
9672
|
* of `collectNonPortableAgentToolRefs` in @runtypelabs/shared, applied to the
|
|
9238
|
-
* step's `config.tools` block and
|
|
9239
|
-
*
|
|
9673
|
+
* step's `config.tools` block, `toolId` and `agentId`). The v1 ensure surface
|
|
9674
|
+
* accepts portable tool references only.
|
|
9240
9675
|
*/
|
|
9241
9676
|
function collectStepNonPortableToolRefs(config, path) {
|
|
9242
9677
|
const found = [];
|
|
@@ -9270,12 +9705,73 @@ function collectStepNonPortableToolRefs(config, path) {
|
|
|
9270
9705
|
}
|
|
9271
9706
|
if (isAccountScoped(config.toolId)) found.push(`${path}.toolId`);
|
|
9272
9707
|
if (isRawId(config.agentId, "agent_")) found.push(`${path}.agentId`);
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9708
|
+
return found;
|
|
9709
|
+
}
|
|
9710
|
+
const CONDITIONAL_BRANCH_STEP_KEYS = [
|
|
9711
|
+
"trueSteps",
|
|
9712
|
+
"falseSteps",
|
|
9713
|
+
"true_steps",
|
|
9714
|
+
"false_steps",
|
|
9715
|
+
"otherwiseSteps"
|
|
9716
|
+
];
|
|
9717
|
+
/** Guards a hostile payload whose nesting is otherwise unbounded. */
|
|
9718
|
+
const MAX_REF_SITE_WALK_DEPTH = 100;
|
|
9719
|
+
/**
|
|
9720
|
+
* Collect account-scoped references at every flow step ref site (inlined copy of
|
|
9721
|
+
* `collectNonPortableFlowStepRefs` in @runtypelabs/shared): loop bodies, conditional
|
|
9722
|
+
* branches, inline flow-as-tool payloads and `errorHandling.fallbacks`.
|
|
9723
|
+
*/
|
|
9724
|
+
function collectFlowStepsNonPortableRefs(steps, basePath, depth = 0) {
|
|
9725
|
+
if (!Array.isArray(steps) || depth > MAX_REF_SITE_WALK_DEPTH) return [];
|
|
9726
|
+
const found = [];
|
|
9727
|
+
steps.forEach((step, index) => {
|
|
9728
|
+
if (!isPlainObject$1(step) || !isPlainObject$1(step.config)) return;
|
|
9729
|
+
found.push(...collectStepConfigSiteRefs(step.type, step.config, `${basePath}[${index}].config`, depth));
|
|
9730
|
+
});
|
|
9731
|
+
return found;
|
|
9732
|
+
}
|
|
9733
|
+
function collectStepConfigSiteRefs(type, config, path, depth) {
|
|
9734
|
+
const found = collectStepNonPortableToolRefs(config, path);
|
|
9735
|
+
found.push(...collectToolBagFlowRefs(config.tools, `${path}.tools`, depth + 1));
|
|
9736
|
+
const errorHandling = config.errorHandling;
|
|
9737
|
+
if (isPlainObject$1(errorHandling) && Array.isArray(errorHandling.fallbacks)) errorHandling.fallbacks.forEach((fallback, i) => {
|
|
9738
|
+
if (!isPlainObject$1(fallback)) return;
|
|
9739
|
+
const fallbackPath = `${path}.errorHandling.fallbacks[${i}]`;
|
|
9740
|
+
if (fallback.type === "flow") {
|
|
9741
|
+
if (typeof fallback.flowId === "string" && fallback.flowId.startsWith("flow_")) found.push(`${fallbackPath}.flowId`);
|
|
9742
|
+
} else if (fallback.type === "step" && isPlainObject$1(fallback.stepConfig)) {
|
|
9743
|
+
found.push(...collectStepNonPortableToolRefs(fallback.stepConfig, `${fallbackPath}.stepConfig`));
|
|
9744
|
+
found.push(...collectToolBagFlowRefs(fallback.stepConfig.tools, `${fallbackPath}.stepConfig.tools`, depth + 1));
|
|
9745
|
+
}
|
|
9746
|
+
});
|
|
9747
|
+
if (type === "loop") found.push(...collectFlowStepsNonPortableRefs(config.steps, `${path}.steps`, depth + 1));
|
|
9748
|
+
else if (type === "conditional") {
|
|
9749
|
+
if (Array.isArray(config.branches)) config.branches.forEach((branch, i) => {
|
|
9750
|
+
if (!isPlainObject$1(branch)) return;
|
|
9751
|
+
found.push(...collectFlowStepsNonPortableRefs(branch.steps, `${path}.branches[${i}].steps`, depth + 1));
|
|
9278
9752
|
});
|
|
9753
|
+
for (const key of CONDITIONAL_BRANCH_STEP_KEYS) found.push(...collectFlowStepsNonPortableRefs(config[key], `${path}.${key}`, depth + 1));
|
|
9754
|
+
}
|
|
9755
|
+
return found;
|
|
9756
|
+
}
|
|
9757
|
+
/** Descend the inline flow-as-tool and skill payloads a step's tool bag can carry. */
|
|
9758
|
+
function collectToolBagFlowRefs(tools, path, depth) {
|
|
9759
|
+
if (!isPlainObject$1(tools) || depth > MAX_REF_SITE_WALK_DEPTH) return [];
|
|
9760
|
+
const found = [];
|
|
9761
|
+
const walkBag = (bag, bagPath, bagDepth) => {
|
|
9762
|
+
if (bagDepth > MAX_REF_SITE_WALK_DEPTH) return;
|
|
9763
|
+
bag.forEach((tool, i) => {
|
|
9764
|
+
if (!isPlainObject$1(tool) || !isPlainObject$1(tool.config)) return;
|
|
9765
|
+
const toolPath = `${bagPath}[${i}].config`;
|
|
9766
|
+
const skill = tool.config.skill;
|
|
9767
|
+
if (isPlainObject$1(skill) && Array.isArray(skill.resolvedTools)) walkBag(skill.resolvedTools, `${toolPath}.skill.resolvedTools`, bagDepth + 1);
|
|
9768
|
+
const flow = tool.config.flow;
|
|
9769
|
+
if (isPlainObject$1(flow) && Array.isArray(flow.steps)) found.push(...collectFlowStepsNonPortableRefs(flow.steps, `${toolPath}.flow.steps`, bagDepth + 1));
|
|
9770
|
+
});
|
|
9771
|
+
};
|
|
9772
|
+
for (const key of ["runtimeTools", "inlineTools"]) {
|
|
9773
|
+
const bag = tools[key];
|
|
9774
|
+
if (Array.isArray(bag)) walkBag(bag, `${path}.${key}`, depth);
|
|
9279
9775
|
}
|
|
9280
9776
|
return found;
|
|
9281
9777
|
}
|
|
@@ -9316,7 +9812,7 @@ function defineFlow(input) {
|
|
|
9316
9812
|
if (unknownStepKeys.length > 0) throw new Error(`defineFlow: steps[${index}] has unknown field(s): ${unknownStepKeys.join(", ")}. Allowed step fields are type, name, order, enabled, when, config. (Step ids are server artifacts and not part of a portable definition.)`);
|
|
9317
9813
|
const config = isPlainObject$1(step.config) ? step.config : void 0;
|
|
9318
9814
|
if (config) {
|
|
9319
|
-
const nonPortable =
|
|
9815
|
+
const nonPortable = collectStepConfigSiteRefs(step.type, config, `steps[${index}].config`, 0);
|
|
9320
9816
|
if (nonPortable.length > 0) throw new Error(`defineFlow: account-scoped reference(s) at ${nonPortable.join(", ")}. Definitions must be environment-portable — tool_…/agent_…/flow_… IDs belong to one account/environment. Use builtin:/platform:/mcp: references, or reference a saved resource by name — tool:<name>, agent:<name>, or flow:<name> instead.`);
|
|
9321
9817
|
}
|
|
9322
9818
|
return {
|
|
@@ -9370,7 +9866,7 @@ var FlowEnsureConflictError = class extends Error {
|
|
|
9370
9866
|
/** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
|
|
9371
9867
|
var FlowDriftError = class extends Error {
|
|
9372
9868
|
constructor(plan) {
|
|
9373
|
-
super(`Flow "${plan.flowId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.flows.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`);
|
|
9869
|
+
super(`Flow "${plan.flowId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${[...plan.changedKeys, ...plan.publishPending ? ["publish (pending)"] : []].join(", ") || "n/a"}). Run client.flows.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`);
|
|
9374
9870
|
this.name = "FlowDriftError";
|
|
9375
9871
|
this.plan = plan;
|
|
9376
9872
|
}
|
|
@@ -9379,7 +9875,7 @@ var FlowDriftError = class extends Error {
|
|
|
9379
9875
|
* Best-effort extraction of `{ status, body }` from the generic request
|
|
9380
9876
|
* error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
|
|
9381
9877
|
*/
|
|
9382
|
-
function parseRequestError$
|
|
9878
|
+
function parseRequestError$6(err) {
|
|
9383
9879
|
if (!(err instanceof Error)) return {
|
|
9384
9880
|
status: null,
|
|
9385
9881
|
body: null
|
|
@@ -9401,8 +9897,8 @@ function parseRequestError$5(err) {
|
|
|
9401
9897
|
};
|
|
9402
9898
|
}
|
|
9403
9899
|
}
|
|
9404
|
-
function toConflictError$
|
|
9405
|
-
const { status, body } = parseRequestError$
|
|
9900
|
+
function toConflictError$6(err) {
|
|
9901
|
+
const { status, body } = parseRequestError$6(err);
|
|
9406
9902
|
if (status !== 409 || !isPlainObject$1(body)) return null;
|
|
9407
9903
|
const code = body.code;
|
|
9408
9904
|
if (code !== "external_modification" && code !== "remote_changed") return null;
|
|
@@ -9418,23 +9914,23 @@ function toConflictError$5(err) {
|
|
|
9418
9914
|
* so a stale server hash from the previous definition version can never be
|
|
9419
9915
|
* probed (which would false-match and silently skip the update).
|
|
9420
9916
|
*/
|
|
9421
|
-
const serverHashMemo$
|
|
9422
|
-
function memoFor$
|
|
9423
|
-
let memo = serverHashMemo$
|
|
9917
|
+
const serverHashMemo$6 = /* @__PURE__ */ new WeakMap();
|
|
9918
|
+
function memoFor$6(client) {
|
|
9919
|
+
let memo = serverHashMemo$6.get(client);
|
|
9424
9920
|
if (!memo) {
|
|
9425
9921
|
memo = /* @__PURE__ */ new Map();
|
|
9426
|
-
serverHashMemo$
|
|
9922
|
+
serverHashMemo$6.set(client, memo);
|
|
9427
9923
|
}
|
|
9428
9924
|
return memo;
|
|
9429
9925
|
}
|
|
9430
9926
|
function memoize$4(memo, memoKey, result) {
|
|
9431
9927
|
if (result.result !== "plan") memo.set(memoKey, result.contentHash);
|
|
9432
9928
|
}
|
|
9433
|
-
async function request$
|
|
9929
|
+
async function request$5(client, body) {
|
|
9434
9930
|
try {
|
|
9435
9931
|
return await client.post("/flows/ensure", body);
|
|
9436
9932
|
} catch (err) {
|
|
9437
|
-
const conflict = toConflictError$
|
|
9933
|
+
const conflict = toConflictError$6(err);
|
|
9438
9934
|
if (conflict) throw conflict;
|
|
9439
9935
|
throw err;
|
|
9440
9936
|
}
|
|
@@ -9459,37 +9955,37 @@ async function ensureFlow(client, definition, options = {}) {
|
|
|
9459
9955
|
steps: definition.steps
|
|
9460
9956
|
};
|
|
9461
9957
|
if (dryRun || expectNoChanges) {
|
|
9462
|
-
const plan = await request$
|
|
9958
|
+
const plan = await request$5(client, {
|
|
9463
9959
|
name: definition.name,
|
|
9464
9960
|
definition: wireDefinition,
|
|
9465
9961
|
dryRun: true,
|
|
9466
9962
|
...passthrough
|
|
9467
9963
|
});
|
|
9468
9964
|
if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
|
|
9469
|
-
if (expectNoChanges && plan.changes !== "none") throw new FlowDriftError(plan);
|
|
9965
|
+
if (expectNoChanges && (plan.changes !== "none" || release === "publish" && plan.publishPending === true)) throw new FlowDriftError(plan);
|
|
9470
9966
|
return plan;
|
|
9471
9967
|
}
|
|
9472
|
-
const memo = memoFor$
|
|
9968
|
+
const memo = memoFor$6(client);
|
|
9473
9969
|
const localHash = await computeFlowContentHash(definition.steps);
|
|
9474
9970
|
const memoKey = `${definition.name} ${localHash}`;
|
|
9475
9971
|
const contentHash = memo.get(memoKey) ?? localHash;
|
|
9476
|
-
const probe = await request$
|
|
9972
|
+
const probe = await request$5(client, {
|
|
9477
9973
|
name: definition.name,
|
|
9478
9974
|
contentHash,
|
|
9479
9975
|
...passthrough
|
|
9480
9976
|
});
|
|
9481
9977
|
if (probe.result !== "definitionRequired") {
|
|
9482
9978
|
memoize$4(memo, memoKey, probe);
|
|
9483
|
-
return convergeInlineEvals(client, definition, probe);
|
|
9979
|
+
return convergeInlineEvals(client, definition, probe, onConflict);
|
|
9484
9980
|
}
|
|
9485
|
-
const converged = await request$
|
|
9981
|
+
const converged = await request$5(client, {
|
|
9486
9982
|
name: definition.name,
|
|
9487
9983
|
definition: wireDefinition,
|
|
9488
9984
|
...passthrough
|
|
9489
9985
|
});
|
|
9490
9986
|
if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
|
|
9491
9987
|
memoize$4(memo, memoKey, converged);
|
|
9492
|
-
return convergeInlineEvals(client, definition, converged);
|
|
9988
|
+
return convergeInlineEvals(client, definition, converged, onConflict);
|
|
9493
9989
|
}
|
|
9494
9990
|
/**
|
|
9495
9991
|
* After the flow itself converges on the real (non-plan) path, converge each
|
|
@@ -9502,10 +9998,10 @@ async function ensureFlow(client, definition, options = {}) {
|
|
|
9502
9998
|
* unchanged (no empty `evals` array). The server's `/flows/ensure` surface is
|
|
9503
9999
|
* never touched by eval semantics — they ride the eval endpoints only.
|
|
9504
10000
|
*/
|
|
9505
|
-
async function convergeInlineEvals(client, definition, result) {
|
|
10001
|
+
async function convergeInlineEvals(client, definition, result, onConflict) {
|
|
9506
10002
|
if (result.result === "plan" || !definition.evals?.length) return result;
|
|
9507
10003
|
const evals = [];
|
|
9508
|
-
for (const evalDef of definition.evals) evals.push(await ensureEval(client, evalDef));
|
|
10004
|
+
for (const evalDef of definition.evals) evals.push(await ensureEval(client, evalDef, onConflict ? { onConflict } : {}));
|
|
9509
10005
|
return {
|
|
9510
10006
|
...result,
|
|
9511
10007
|
evals
|
|
@@ -10741,24 +11237,8 @@ var EvalsNamespace = class {
|
|
|
10741
11237
|
run(config) {
|
|
10742
11238
|
return new EvalRunner(this.getClient, config);
|
|
10743
11239
|
}
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
* the deploy-time, non-executing converge for code-colocated evals. Hash-first:
|
|
10747
|
-
* the steady state is one tiny probe request. Upserts the eval suite + replaces
|
|
10748
|
-
* its cases; never runs the eval.
|
|
10749
|
-
*
|
|
10750
|
-
* @example
|
|
10751
|
-
* ```typescript
|
|
10752
|
-
* const suite = defineEval({
|
|
10753
|
-
* target: { flow: 'support-triage' },
|
|
10754
|
-
* graders: [noError()],
|
|
10755
|
-
* cases: [{ name: 'billing', input: { variables: { message: 'I was double charged' } }, expect: [contains('finance')] }],
|
|
10756
|
-
* })
|
|
10757
|
-
* const result = await Runtype.evals.ensure(suite)
|
|
10758
|
-
* ```
|
|
10759
|
-
*/
|
|
10760
|
-
async ensure(definition) {
|
|
10761
|
-
return ensureEval(this.getClient(), definition);
|
|
11240
|
+
async ensure(definition, options = {}) {
|
|
11241
|
+
return ensureEval(this.getClient(), definition, options);
|
|
10762
11242
|
}
|
|
10763
11243
|
/**
|
|
10764
11244
|
* Pull the canonical definition + provenance for an eval suite by name — the
|
|
@@ -11025,7 +11505,7 @@ var SkillDriftError = class extends Error {
|
|
|
11025
11505
|
* Best-effort extraction of `{ status, body }` from the generic request error
|
|
11026
11506
|
* thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
|
|
11027
11507
|
*/
|
|
11028
|
-
function parseRequestError$
|
|
11508
|
+
function parseRequestError$5(err) {
|
|
11029
11509
|
if (!(err instanceof Error)) return {
|
|
11030
11510
|
status: null,
|
|
11031
11511
|
body: null
|
|
@@ -11047,8 +11527,8 @@ function parseRequestError$4(err) {
|
|
|
11047
11527
|
};
|
|
11048
11528
|
}
|
|
11049
11529
|
}
|
|
11050
|
-
function toConflictError$
|
|
11051
|
-
const { status, body } = parseRequestError$
|
|
11530
|
+
function toConflictError$5(err) {
|
|
11531
|
+
const { status, body } = parseRequestError$5(err);
|
|
11052
11532
|
if (status !== 409 || !isPlainObject$1(body)) return null;
|
|
11053
11533
|
const code = body.code;
|
|
11054
11534
|
if (code !== "external_modification" && code !== "remote_changed") return null;
|
|
@@ -11060,23 +11540,23 @@ function toConflictError$4(err) {
|
|
|
11060
11540
|
* within the process lifetime invalidates a stale server hash (which would
|
|
11061
11541
|
* false-match and silently skip the update). Same scheme as flows/tools.ensure.
|
|
11062
11542
|
*/
|
|
11063
|
-
const serverHashMemo$
|
|
11064
|
-
function memoFor$
|
|
11065
|
-
let memo = serverHashMemo$
|
|
11543
|
+
const serverHashMemo$5 = /* @__PURE__ */ new WeakMap();
|
|
11544
|
+
function memoFor$5(client) {
|
|
11545
|
+
let memo = serverHashMemo$5.get(client);
|
|
11066
11546
|
if (!memo) {
|
|
11067
11547
|
memo = /* @__PURE__ */ new Map();
|
|
11068
|
-
serverHashMemo$
|
|
11548
|
+
serverHashMemo$5.set(client, memo);
|
|
11069
11549
|
}
|
|
11070
11550
|
return memo;
|
|
11071
11551
|
}
|
|
11072
11552
|
function memoize$3(memo, memoKey, result) {
|
|
11073
11553
|
if (result.result !== "plan") memo.set(memoKey, result.contentHash);
|
|
11074
11554
|
}
|
|
11075
|
-
async function request$
|
|
11555
|
+
async function request$4(client, body) {
|
|
11076
11556
|
try {
|
|
11077
11557
|
return await client.post("/skills/ensure", body);
|
|
11078
11558
|
} catch (err) {
|
|
11079
|
-
const conflict = toConflictError$
|
|
11559
|
+
const conflict = toConflictError$5(err);
|
|
11080
11560
|
if (conflict) throw conflict;
|
|
11081
11561
|
throw err;
|
|
11082
11562
|
}
|
|
@@ -11088,14 +11568,15 @@ async function request$3(client, body) {
|
|
|
11088
11568
|
* on every change; never deletes.
|
|
11089
11569
|
*/
|
|
11090
11570
|
async function ensureSkill(client, definition, options = {}) {
|
|
11091
|
-
const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
|
|
11571
|
+
const { dryRun, onConflict, release, expectedRemoteHash, version, expectNoChanges } = options;
|
|
11092
11572
|
const passthrough = {
|
|
11093
11573
|
...onConflict ? { onConflict } : {},
|
|
11094
11574
|
...release ? { release } : {},
|
|
11095
|
-
...expectedRemoteHash ? { expectedRemoteHash } : {}
|
|
11575
|
+
...expectedRemoteHash ? { expectedRemoteHash } : {},
|
|
11576
|
+
...version ? { version } : {}
|
|
11096
11577
|
};
|
|
11097
11578
|
if (dryRun || expectNoChanges) {
|
|
11098
|
-
const plan = await request$
|
|
11579
|
+
const plan = await request$4(client, {
|
|
11099
11580
|
name: definition.name,
|
|
11100
11581
|
definition: manifestToWire(definition),
|
|
11101
11582
|
dryRun: true,
|
|
@@ -11105,11 +11586,11 @@ async function ensureSkill(client, definition, options = {}) {
|
|
|
11105
11586
|
if (expectNoChanges && plan.changes !== "none") throw new SkillDriftError(plan);
|
|
11106
11587
|
return plan;
|
|
11107
11588
|
}
|
|
11108
|
-
const memo = memoFor$
|
|
11589
|
+
const memo = memoFor$5(client);
|
|
11109
11590
|
const localHash = await computeSkillContentHash(definition);
|
|
11110
11591
|
const memoKey = `${definition.name} ${localHash}`;
|
|
11111
11592
|
const contentHash = memo.get(memoKey) ?? localHash;
|
|
11112
|
-
const probe = await request$
|
|
11593
|
+
const probe = await request$4(client, {
|
|
11113
11594
|
name: definition.name,
|
|
11114
11595
|
contentHash,
|
|
11115
11596
|
...passthrough
|
|
@@ -11118,7 +11599,7 @@ async function ensureSkill(client, definition, options = {}) {
|
|
|
11118
11599
|
memoize$3(memo, memoKey, probe);
|
|
11119
11600
|
return probe;
|
|
11120
11601
|
}
|
|
11121
|
-
const converged = await request$
|
|
11602
|
+
const converged = await request$4(client, {
|
|
11122
11603
|
name: definition.name,
|
|
11123
11604
|
definition: manifestToWire(definition),
|
|
11124
11605
|
...passthrough
|
|
@@ -11534,6 +12015,18 @@ function defineAgent(input) {
|
|
|
11534
12015
|
config
|
|
11535
12016
|
};
|
|
11536
12017
|
}
|
|
12018
|
+
function planHasSkillBindingChanges(plan) {
|
|
12019
|
+
return (plan.skillBindings ?? []).some((binding) => binding.action !== "unchanged");
|
|
12020
|
+
}
|
|
12021
|
+
/** Whether a dry run that asked for a deploy would move the alias or change its binding names. */
|
|
12022
|
+
function planHasDeploymentChanges(plan) {
|
|
12023
|
+
return plan.deployment?.changed === true || plan.deployment?.bindingsChanged === true;
|
|
12024
|
+
}
|
|
12025
|
+
function deploymentChangeLabels(plan) {
|
|
12026
|
+
const deployment = plan.deployment;
|
|
12027
|
+
if (!deployment) return [];
|
|
12028
|
+
return [...deployment.changed ? [`deploy:${deployment.alias} (pointer)`] : [], ...deployment.bindingsChanged ? [`deploy:${deployment.alias} (bindings)`] : []];
|
|
12029
|
+
}
|
|
11537
12030
|
/** A 409 from the ensure protocol (external_modification or remote_changed). */
|
|
11538
12031
|
var AgentEnsureConflictError = class extends Error {
|
|
11539
12032
|
constructor(body) {
|
|
@@ -11548,7 +12041,12 @@ var AgentEnsureConflictError = class extends Error {
|
|
|
11548
12041
|
/** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
|
|
11549
12042
|
var AgentDriftError = class extends Error {
|
|
11550
12043
|
constructor(plan) {
|
|
11551
|
-
|
|
12044
|
+
const skillChanges = (plan.skillBindings ?? []).filter((binding) => binding.action !== "unchanged").map((binding) => `skill:${binding.name} (${binding.action})`);
|
|
12045
|
+
super(`Agent "${plan.agentId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${[
|
|
12046
|
+
...plan.changedKeys,
|
|
12047
|
+
...skillChanges,
|
|
12048
|
+
...deploymentChangeLabels(plan)
|
|
12049
|
+
].join(", ") || "n/a"}). Run client.agents.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`);
|
|
11552
12050
|
this.name = "AgentDriftError";
|
|
11553
12051
|
this.plan = plan;
|
|
11554
12052
|
}
|
|
@@ -11557,7 +12055,7 @@ var AgentDriftError = class extends Error {
|
|
|
11557
12055
|
* Best-effort extraction of `{ status, body }` from the generic request
|
|
11558
12056
|
* error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
|
|
11559
12057
|
*/
|
|
11560
|
-
function parseRequestError$
|
|
12058
|
+
function parseRequestError$4(err) {
|
|
11561
12059
|
if (!(err instanceof Error)) return {
|
|
11562
12060
|
status: null,
|
|
11563
12061
|
body: null
|
|
@@ -11579,8 +12077,8 @@ function parseRequestError$3(err) {
|
|
|
11579
12077
|
};
|
|
11580
12078
|
}
|
|
11581
12079
|
}
|
|
11582
|
-
function toConflictError$
|
|
11583
|
-
const { status, body } = parseRequestError$
|
|
12080
|
+
function toConflictError$4(err) {
|
|
12081
|
+
const { status, body } = parseRequestError$4(err);
|
|
11584
12082
|
if (status !== 409 || !isPlainObject(body)) return null;
|
|
11585
12083
|
const code = body.code;
|
|
11586
12084
|
if (code !== "external_modification" && code !== "remote_changed") return null;
|
|
@@ -11596,12 +12094,12 @@ function toConflictError$3(err) {
|
|
|
11596
12094
|
* so a stale server hash from the previous definition version can never be
|
|
11597
12095
|
* probed (which would false-match and silently skip the update).
|
|
11598
12096
|
*/
|
|
11599
|
-
const serverHashMemo$
|
|
11600
|
-
function memoFor$
|
|
11601
|
-
let memo = serverHashMemo$
|
|
12097
|
+
const serverHashMemo$4 = /* @__PURE__ */ new WeakMap();
|
|
12098
|
+
function memoFor$4(client) {
|
|
12099
|
+
let memo = serverHashMemo$4.get(client);
|
|
11602
12100
|
if (!memo) {
|
|
11603
12101
|
memo = /* @__PURE__ */ new Map();
|
|
11604
|
-
serverHashMemo$
|
|
12102
|
+
serverHashMemo$4.set(client, memo);
|
|
11605
12103
|
}
|
|
11606
12104
|
return memo;
|
|
11607
12105
|
}
|
|
@@ -11637,14 +12135,16 @@ var AgentsNamespace = class {
|
|
|
11637
12135
|
*/
|
|
11638
12136
|
async ensure(definition, options = {}) {
|
|
11639
12137
|
const client = this.getClient();
|
|
11640
|
-
const { dryRun, onConflict, release, deploy, expectedRemoteHash, version, expectNoChanges } = options;
|
|
12138
|
+
const { dryRun, onConflict, release, deploy, expectedRemoteHash, version, expectNoChanges, skills, capabilities } = options;
|
|
11641
12139
|
if (release !== void 0 && deploy !== void 0) throw new Error(ENSURE_RELEASE_DEPLOY_CONFLICT_MESSAGE);
|
|
11642
12140
|
const passthrough = {
|
|
11643
12141
|
...onConflict ? { onConflict } : {},
|
|
11644
12142
|
...release ? { release } : {},
|
|
11645
12143
|
...deploy ? { deploy } : {},
|
|
11646
12144
|
...expectedRemoteHash ? { expectedRemoteHash } : {},
|
|
11647
|
-
...version ? { version } : {}
|
|
12145
|
+
...version ? { version } : {},
|
|
12146
|
+
...skills ? { skills } : {},
|
|
12147
|
+
...capabilities ? { capabilities } : {}
|
|
11648
12148
|
};
|
|
11649
12149
|
if (dryRun || expectNoChanges) {
|
|
11650
12150
|
const plan = await this.request(client, {
|
|
@@ -11654,15 +12154,16 @@ var AgentsNamespace = class {
|
|
|
11654
12154
|
...passthrough
|
|
11655
12155
|
});
|
|
11656
12156
|
if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
|
|
11657
|
-
|
|
12157
|
+
const deployRequested = release === "publish" || deploy !== void 0;
|
|
12158
|
+
if (expectNoChanges && (plan.changes !== "none" || planHasSkillBindingChanges(plan) || deployRequested && planHasDeploymentChanges(plan))) throw new AgentDriftError(plan);
|
|
11658
12159
|
return plan;
|
|
11659
12160
|
}
|
|
11660
|
-
const memo = memoFor$
|
|
12161
|
+
const memo = memoFor$4(client);
|
|
11661
12162
|
const localHash = await computeAgentContentHash({
|
|
11662
12163
|
...definition,
|
|
11663
12164
|
config: definition.config
|
|
11664
12165
|
});
|
|
11665
|
-
const memoKey = `${definition.name}\u0000${localHash}`;
|
|
12166
|
+
const memoKey = `${definition.name}\u0000${localHash}${capabilities ? `\u0000${JSON.stringify(capabilities)}` : ""}`;
|
|
11666
12167
|
const contentHash = memo.get(memoKey) ?? localHash;
|
|
11667
12168
|
const probe = await this.request(client, {
|
|
11668
12169
|
name: definition.name,
|
|
@@ -11696,7 +12197,7 @@ var AgentsNamespace = class {
|
|
|
11696
12197
|
try {
|
|
11697
12198
|
return await client.post("/agents/ensure", body);
|
|
11698
12199
|
} catch (err) {
|
|
11699
|
-
const conflict = toConflictError$
|
|
12200
|
+
const conflict = toConflictError$4(err);
|
|
11700
12201
|
if (conflict) throw conflict;
|
|
11701
12202
|
throw err;
|
|
11702
12203
|
}
|
|
@@ -11825,7 +12326,7 @@ var ToolDriftError = class extends Error {
|
|
|
11825
12326
|
* Best-effort extraction of `{ status, body }` from the generic request
|
|
11826
12327
|
* error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
|
|
11827
12328
|
*/
|
|
11828
|
-
function parseRequestError$
|
|
12329
|
+
function parseRequestError$3(err) {
|
|
11829
12330
|
if (!(err instanceof Error)) return {
|
|
11830
12331
|
status: null,
|
|
11831
12332
|
body: null
|
|
@@ -11847,8 +12348,8 @@ function parseRequestError$2(err) {
|
|
|
11847
12348
|
};
|
|
11848
12349
|
}
|
|
11849
12350
|
}
|
|
11850
|
-
function toConflictError$
|
|
11851
|
-
const { status, body } = parseRequestError$
|
|
12351
|
+
function toConflictError$3(err) {
|
|
12352
|
+
const { status, body } = parseRequestError$3(err);
|
|
11852
12353
|
if (status !== 409 || !isPlainObject$1(body)) return null;
|
|
11853
12354
|
const code = body.code;
|
|
11854
12355
|
if (code !== "external_modification" && code !== "remote_changed") return null;
|
|
@@ -11860,23 +12361,23 @@ function toConflictError$2(err) {
|
|
|
11860
12361
|
* within the process lifetime invalidates a stale server hash (which would
|
|
11861
12362
|
* false-match and silently skip the update). Same scheme as flows.ensure.
|
|
11862
12363
|
*/
|
|
11863
|
-
const serverHashMemo$
|
|
11864
|
-
function memoFor$
|
|
11865
|
-
let memo = serverHashMemo$
|
|
12364
|
+
const serverHashMemo$3 = /* @__PURE__ */ new WeakMap();
|
|
12365
|
+
function memoFor$3(client) {
|
|
12366
|
+
let memo = serverHashMemo$3.get(client);
|
|
11866
12367
|
if (!memo) {
|
|
11867
12368
|
memo = /* @__PURE__ */ new Map();
|
|
11868
|
-
serverHashMemo$
|
|
12369
|
+
serverHashMemo$3.set(client, memo);
|
|
11869
12370
|
}
|
|
11870
12371
|
return memo;
|
|
11871
12372
|
}
|
|
11872
12373
|
function memoize$2(memo, memoKey, result) {
|
|
11873
12374
|
if (result.result !== "plan") memo.set(memoKey, result.contentHash);
|
|
11874
12375
|
}
|
|
11875
|
-
async function request$
|
|
12376
|
+
async function request$3(client, body) {
|
|
11876
12377
|
try {
|
|
11877
12378
|
return await client.post("/tools/ensure", body);
|
|
11878
12379
|
} catch (err) {
|
|
11879
|
-
const conflict = toConflictError$
|
|
12380
|
+
const conflict = toConflictError$3(err);
|
|
11880
12381
|
if (conflict) throw conflict;
|
|
11881
12382
|
throw err;
|
|
11882
12383
|
}
|
|
@@ -11893,7 +12394,7 @@ async function ensureTool(client, definition, options = {}) {
|
|
|
11893
12394
|
...expectedRemoteHash ? { expectedRemoteHash } : {}
|
|
11894
12395
|
};
|
|
11895
12396
|
if (dryRun || expectNoChanges) {
|
|
11896
|
-
const plan = await request$
|
|
12397
|
+
const plan = await request$3(client, {
|
|
11897
12398
|
name: definition.name,
|
|
11898
12399
|
definition,
|
|
11899
12400
|
dryRun: true,
|
|
@@ -11903,11 +12404,11 @@ async function ensureTool(client, definition, options = {}) {
|
|
|
11903
12404
|
if (expectNoChanges && plan.changes !== "none") throw new ToolDriftError(plan);
|
|
11904
12405
|
return plan;
|
|
11905
12406
|
}
|
|
11906
|
-
const memo = memoFor$
|
|
12407
|
+
const memo = memoFor$3(client);
|
|
11907
12408
|
const localHash = await computeToolContentHash(definition);
|
|
11908
12409
|
const memoKey = `${definition.name} ${localHash}`;
|
|
11909
12410
|
const contentHash = memo.get(memoKey) ?? localHash;
|
|
11910
|
-
const probe = await request$
|
|
12411
|
+
const probe = await request$3(client, {
|
|
11911
12412
|
name: definition.name,
|
|
11912
12413
|
contentHash,
|
|
11913
12414
|
...passthrough
|
|
@@ -11916,7 +12417,7 @@ async function ensureTool(client, definition, options = {}) {
|
|
|
11916
12417
|
memoize$2(memo, memoKey, probe);
|
|
11917
12418
|
return probe;
|
|
11918
12419
|
}
|
|
11919
|
-
const converged = await request$
|
|
12420
|
+
const converged = await request$3(client, {
|
|
11920
12421
|
name: definition.name,
|
|
11921
12422
|
definition,
|
|
11922
12423
|
...passthrough
|
|
@@ -11972,10 +12473,197 @@ var ToolsNamespace = class {
|
|
|
11972
12473
|
}
|
|
11973
12474
|
};
|
|
11974
12475
|
//#endregion
|
|
12476
|
+
//#region src/question-sets-ensure.ts
|
|
12477
|
+
/** Canonical form of a question set definition, without its slug. */
|
|
12478
|
+
function normalizeQuestionSetDefinition(definition) {
|
|
12479
|
+
return normalizeValue$1({
|
|
12480
|
+
name: definition.name,
|
|
12481
|
+
description: definition.description,
|
|
12482
|
+
questions: isPlainObject$1(definition.questions) ? definition.questions : {},
|
|
12483
|
+
inputMapping: definition.inputMapping,
|
|
12484
|
+
model: definition.model
|
|
12485
|
+
});
|
|
12486
|
+
}
|
|
12487
|
+
/** SHA-256 (hex) over the canonical question set definition. */
|
|
12488
|
+
async function computeQuestionSetContentHash(definition) {
|
|
12489
|
+
return sha256Hex(JSON.stringify(normalizeQuestionSetDefinition(definition)));
|
|
12490
|
+
}
|
|
12491
|
+
const SLUG_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
12492
|
+
const DEFINE_QUESTION_SET_KEYS = /* @__PURE__ */ new Set([
|
|
12493
|
+
"slug",
|
|
12494
|
+
"name",
|
|
12495
|
+
"description",
|
|
12496
|
+
"questions",
|
|
12497
|
+
"inputMapping",
|
|
12498
|
+
"model"
|
|
12499
|
+
]);
|
|
12500
|
+
/**
|
|
12501
|
+
* Pure local constructor for a question set definition. Checks the structure
|
|
12502
|
+
* and rejects unknown fields; the server validates the questions at ensure time.
|
|
12503
|
+
*
|
|
12504
|
+
* @example
|
|
12505
|
+
* ```typescript
|
|
12506
|
+
* const triage = defineQuestionSet({
|
|
12507
|
+
* slug: 'ticket-triage',
|
|
12508
|
+
* name: 'Ticket triage',
|
|
12509
|
+
* questions: { urgent: { type: 'boolean', instructions: 'Is the ticket urgent?' } },
|
|
12510
|
+
* })
|
|
12511
|
+
* ```
|
|
12512
|
+
*/
|
|
12513
|
+
function defineQuestionSet(input) {
|
|
12514
|
+
if (!input || typeof input !== "object") throw new Error("defineQuestionSet requires a definition object");
|
|
12515
|
+
if (typeof input.slug !== "string" || !SLUG_PATTERN.test(input.slug)) throw new Error("defineQuestionSet requires a \"slug\" of lowercase letters, digits, \"-\" and \"_\", up to 64 characters");
|
|
12516
|
+
if (typeof input.name !== "string" || input.name.length === 0) throw new Error("defineQuestionSet requires a non-empty string \"name\"");
|
|
12517
|
+
if (!isPlainObject$1(input.questions) || Object.keys(input.questions).length === 0) throw new Error("defineQuestionSet requires a non-empty \"questions\" object");
|
|
12518
|
+
const unknownKeys = Object.keys(input).filter((key) => !DEFINE_QUESTION_SET_KEYS.has(key));
|
|
12519
|
+
if (unknownKeys.length > 0) throw new Error(`defineQuestionSet: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are slug, name, description, questions, inputMapping and model.`);
|
|
12520
|
+
return { ...input };
|
|
12521
|
+
}
|
|
12522
|
+
/** A 409 from the ensure protocol (external_modification or remote_changed). */
|
|
12523
|
+
var QuestionSetEnsureConflictError = class extends Error {
|
|
12524
|
+
constructor(body) {
|
|
12525
|
+
super(body.error ?? `Question set ensure conflict: ${body.code}`);
|
|
12526
|
+
this.name = "QuestionSetEnsureConflictError";
|
|
12527
|
+
this.code = body.code;
|
|
12528
|
+
this.lastModifiedSource = body.lastModifiedSource;
|
|
12529
|
+
this.modifiedAt = body.modifiedAt;
|
|
12530
|
+
this.currentHash = body.currentHash;
|
|
12531
|
+
}
|
|
12532
|
+
};
|
|
12533
|
+
/** Thrown by `ensure(def, { expectNoChanges: true })` when the remote differs. */
|
|
12534
|
+
var QuestionSetDriftError = class extends Error {
|
|
12535
|
+
constructor(slug, plan) {
|
|
12536
|
+
super(`Question set "${slug}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.questionSets.pull(slug) to absorb the remote edit, or re-run ensure to converge.`);
|
|
12537
|
+
this.name = "QuestionSetDriftError";
|
|
12538
|
+
this.plan = plan;
|
|
12539
|
+
}
|
|
12540
|
+
};
|
|
12541
|
+
function toConflictError$2(err) {
|
|
12542
|
+
if (!(err instanceof Error)) return null;
|
|
12543
|
+
const match = err.message.match(/^API request failed: (409) .*? - ([\s\S]*)$/);
|
|
12544
|
+
if (!match) return null;
|
|
12545
|
+
let body;
|
|
12546
|
+
try {
|
|
12547
|
+
body = JSON.parse(match[2]);
|
|
12548
|
+
} catch {
|
|
12549
|
+
return null;
|
|
12550
|
+
}
|
|
12551
|
+
if (!isPlainObject$1(body)) return null;
|
|
12552
|
+
if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
|
|
12553
|
+
return new QuestionSetEnsureConflictError(body);
|
|
12554
|
+
}
|
|
12555
|
+
const serverHashMemo$2 = /* @__PURE__ */ new WeakMap();
|
|
12556
|
+
function memoFor$2(client) {
|
|
12557
|
+
let memo = serverHashMemo$2.get(client);
|
|
12558
|
+
if (!memo) {
|
|
12559
|
+
memo = /* @__PURE__ */ new Map();
|
|
12560
|
+
serverHashMemo$2.set(client, memo);
|
|
12561
|
+
}
|
|
12562
|
+
return memo;
|
|
12563
|
+
}
|
|
12564
|
+
async function request$2(client, body) {
|
|
12565
|
+
try {
|
|
12566
|
+
return await client.post("/question-sets/ensure", body);
|
|
12567
|
+
} catch (err) {
|
|
12568
|
+
throw toConflictError$2(err) ?? err;
|
|
12569
|
+
}
|
|
12570
|
+
}
|
|
12571
|
+
function toWireDefinition(definition) {
|
|
12572
|
+
const { slug: _slug, ...rest } = definition;
|
|
12573
|
+
return rest;
|
|
12574
|
+
}
|
|
12575
|
+
/**
|
|
12576
|
+
* Idempotently converge a question set onto the platform. Probes with a
|
|
12577
|
+
* content hash first and sends the full definition only on a miss. Appends a
|
|
12578
|
+
* version when the questions, input mapping or model change; never deletes.
|
|
12579
|
+
*/
|
|
12580
|
+
async function ensureQuestionSet(client, definition, options = {}) {
|
|
12581
|
+
const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
|
|
12582
|
+
const passthrough = {
|
|
12583
|
+
...onConflict ? { onConflict } : {},
|
|
12584
|
+
...release ? { release } : {},
|
|
12585
|
+
...expectedRemoteHash ? { expectedRemoteHash } : {}
|
|
12586
|
+
};
|
|
12587
|
+
if (dryRun || expectNoChanges) {
|
|
12588
|
+
const plan = await request$2(client, {
|
|
12589
|
+
slug: definition.slug,
|
|
12590
|
+
definition: toWireDefinition(definition),
|
|
12591
|
+
dryRun: true,
|
|
12592
|
+
...passthrough
|
|
12593
|
+
});
|
|
12594
|
+
if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
|
|
12595
|
+
if (expectNoChanges && plan.changes !== "none") throw new QuestionSetDriftError(definition.slug, plan);
|
|
12596
|
+
return plan;
|
|
12597
|
+
}
|
|
12598
|
+
const memo = memoFor$2(client);
|
|
12599
|
+
const localHash = await computeQuestionSetContentHash(definition);
|
|
12600
|
+
const memoKey = `${definition.slug} ${localHash}`;
|
|
12601
|
+
const probe = await request$2(client, {
|
|
12602
|
+
slug: definition.slug,
|
|
12603
|
+
contentHash: memo.get(memoKey) ?? localHash,
|
|
12604
|
+
...passthrough
|
|
12605
|
+
});
|
|
12606
|
+
if (probe.result !== "definitionRequired") {
|
|
12607
|
+
if (probe.result !== "plan") memo.set(memoKey, probe.contentHash);
|
|
12608
|
+
return probe;
|
|
12609
|
+
}
|
|
12610
|
+
const converged = await request$2(client, {
|
|
12611
|
+
slug: definition.slug,
|
|
12612
|
+
definition: toWireDefinition(definition),
|
|
12613
|
+
...passthrough
|
|
12614
|
+
});
|
|
12615
|
+
if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
|
|
12616
|
+
if (converged.result !== "plan") memo.set(memoKey, converged.contentHash);
|
|
12617
|
+
return converged;
|
|
12618
|
+
}
|
|
12619
|
+
/** Pull the canonical definition and provenance for a question set by slug. */
|
|
12620
|
+
async function pullQuestionSet(client, slug) {
|
|
12621
|
+
return client.get("/question-sets/pull", { slug });
|
|
12622
|
+
}
|
|
12623
|
+
//#endregion
|
|
12624
|
+
//#region src/question-sets-namespace.ts
|
|
12625
|
+
/** Question set config-as-code: `ensure` and `pull`. */
|
|
12626
|
+
var QuestionSetsNamespace = class {
|
|
12627
|
+
constructor(getClient) {
|
|
12628
|
+
this.getClient = getClient;
|
|
12629
|
+
}
|
|
12630
|
+
/**
|
|
12631
|
+
* Converge a `defineQuestionSet` definition. The steady state is one hash
|
|
12632
|
+
* probe. Pass `release: 'publish'` so flows and agents can resolve the
|
|
12633
|
+
* latest version.
|
|
12634
|
+
*
|
|
12635
|
+
* @example
|
|
12636
|
+
* ```typescript
|
|
12637
|
+
* await Runtype.questionSets.ensure(triage, { release: 'publish' })
|
|
12638
|
+
* await Runtype.questionSets.ensure(triage, { expectNoChanges: true })
|
|
12639
|
+
* ```
|
|
12640
|
+
*/
|
|
12641
|
+
async ensure(definition, options = {}) {
|
|
12642
|
+
return ensureQuestionSet(this.getClient(), definition, options);
|
|
12643
|
+
}
|
|
12644
|
+
/** Pull the live definition and provenance for a slug, to absorb a dashboard edit. */
|
|
12645
|
+
async pull(slug) {
|
|
12646
|
+
return pullQuestionSet(this.getClient(), slug);
|
|
12647
|
+
}
|
|
12648
|
+
};
|
|
12649
|
+
//#endregion
|
|
11975
12650
|
//#region src/products-ensure.ts
|
|
12651
|
+
/**
|
|
12652
|
+
* `spec` with its stack as a save stores it: without the server-set fields (`detected`, `declared`,
|
|
12653
|
+
* `hiddenAt`), which their own write paths set, and with `hidden` deduplicated.
|
|
12654
|
+
*/
|
|
12655
|
+
function asSavedStack(spec) {
|
|
12656
|
+
if (!isPlainObject$1(spec.stack)) return spec;
|
|
12657
|
+
const { detected: _detected, declared: _declared, hiddenAt: _hiddenAt, ...stack } = spec.stack;
|
|
12658
|
+
if (Array.isArray(stack.hidden)) stack.hidden = [...new Set(stack.hidden)];
|
|
12659
|
+
return {
|
|
12660
|
+
...spec,
|
|
12661
|
+
stack
|
|
12662
|
+
};
|
|
12663
|
+
}
|
|
11976
12664
|
/** Canonical normalized form of a product definition (name + canvas excluded). */
|
|
11977
12665
|
function normalizeProductDefinition(definition) {
|
|
11978
|
-
const spec = isPlainObject$1(definition.spec) ? normalizeValue$1(definition.spec) : {};
|
|
12666
|
+
const spec = isPlainObject$1(definition.spec) ? normalizeValue$1(asSavedStack(definition.spec)) : {};
|
|
11979
12667
|
return {
|
|
11980
12668
|
...definition.description ? { description: definition.description } : {},
|
|
11981
12669
|
...definition.icon ? { icon: definition.icon } : {},
|
|
@@ -12045,7 +12733,7 @@ var ProductDriftError = class extends Error {
|
|
|
12045
12733
|
* Best-effort extraction of `{ status, body }` from the generic request
|
|
12046
12734
|
* error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
|
|
12047
12735
|
*/
|
|
12048
|
-
function parseRequestError$
|
|
12736
|
+
function parseRequestError$2(err) {
|
|
12049
12737
|
if (!(err instanceof Error)) return {
|
|
12050
12738
|
status: null,
|
|
12051
12739
|
body: null
|
|
@@ -12068,7 +12756,7 @@ function parseRequestError$1(err) {
|
|
|
12068
12756
|
}
|
|
12069
12757
|
}
|
|
12070
12758
|
function toConflictError$1(err) {
|
|
12071
|
-
const { status, body } = parseRequestError$
|
|
12759
|
+
const { status, body } = parseRequestError$2(err);
|
|
12072
12760
|
if (status !== 409 || !isPlainObject$1(body)) return null;
|
|
12073
12761
|
const code = body.code;
|
|
12074
12762
|
if (code !== "external_modification" && code !== "remote_changed") return null;
|
|
@@ -12190,20 +12878,85 @@ function defineFpo(fpo) {
|
|
|
12190
12878
|
if (!product || typeof product.name !== "string" || product.name.length === 0) throw new Error("defineFpo requires a non-empty \"product.name\" (the converge identity)");
|
|
12191
12879
|
return fpo;
|
|
12192
12880
|
}
|
|
12881
|
+
const FPO_DRIFT_RESULTS = /* @__PURE__ */ new Set([
|
|
12882
|
+
"created",
|
|
12883
|
+
"updated",
|
|
12884
|
+
"pruned",
|
|
12885
|
+
"failed"
|
|
12886
|
+
]);
|
|
12887
|
+
/** Thrown by `ensureFpo(fpo, { expectNoChanges: true })` when the plan is not clean. */
|
|
12888
|
+
var FpoDriftError = class extends Error {
|
|
12889
|
+
constructor(plan) {
|
|
12890
|
+
const drifted = plan.entities.filter((entity) => FPO_DRIFT_RESULTS.has(entity.result)).map((entity) => `${entity.kind}:${entity.name} (${entity.result})`);
|
|
12891
|
+
super(`Product "${plan.productId ?? "definition"}" drifted (${drifted.join(", ")}). Run client.products.pullFpo(name) to absorb the remote edit into your repo, or re-run ensureFpo to converge.`);
|
|
12892
|
+
this.name = "FpoDriftError";
|
|
12893
|
+
this.plan = plan;
|
|
12894
|
+
}
|
|
12895
|
+
};
|
|
12896
|
+
function fpoPlanHasDrift(plan) {
|
|
12897
|
+
return plan.entities.some((entity) => FPO_DRIFT_RESULTS.has(entity.result));
|
|
12898
|
+
}
|
|
12899
|
+
function parseRequestError$1(err) {
|
|
12900
|
+
if (!(err instanceof Error)) return {
|
|
12901
|
+
status: null,
|
|
12902
|
+
body: null
|
|
12903
|
+
};
|
|
12904
|
+
const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
|
|
12905
|
+
if (!match) return {
|
|
12906
|
+
status: null,
|
|
12907
|
+
body: null
|
|
12908
|
+
};
|
|
12909
|
+
try {
|
|
12910
|
+
return {
|
|
12911
|
+
status: Number(match[1]),
|
|
12912
|
+
body: JSON.parse(match[2])
|
|
12913
|
+
};
|
|
12914
|
+
} catch {
|
|
12915
|
+
return {
|
|
12916
|
+
status: Number(match[1]),
|
|
12917
|
+
body: null
|
|
12918
|
+
};
|
|
12919
|
+
}
|
|
12920
|
+
}
|
|
12921
|
+
async function requestFpoEnsure(client, body) {
|
|
12922
|
+
try {
|
|
12923
|
+
return await client.post("/products/ensure-fpo", body);
|
|
12924
|
+
} catch (err) {
|
|
12925
|
+
const { status, body: errorBody } = parseRequestError$1(err);
|
|
12926
|
+
if (status === 409 && isPlainObject$1(errorBody) && (errorBody.code === "external_modification" || errorBody.code === "remote_changed")) throw new ProductEnsureConflictError(errorBody);
|
|
12927
|
+
throw err;
|
|
12928
|
+
}
|
|
12929
|
+
}
|
|
12193
12930
|
/**
|
|
12194
|
-
* Converge an entire FPO onto the platform. Ships the full FPO
|
|
12195
|
-
*
|
|
12196
|
-
* and returns the whole-FPO hash + per-entity report.
|
|
12197
|
-
* `result.hasFailures` / `entities[].result` and re-run to self-heal.
|
|
12931
|
+
* Converge an entire FPO onto the platform. Ships the full FPO unless a
|
|
12932
|
+
* drafts-only `hashProbe` finds the live graph already matches; the server fans out to the
|
|
12933
|
+
* per-entity ensure services and returns the whole-FPO hash + per-entity report.
|
|
12934
|
+
* Non-atomic: inspect `result.hasFailures` / `entities[].result` and re-run to self-heal.
|
|
12198
12935
|
*/
|
|
12199
12936
|
async function ensureFpo(client, fpo, options = {}) {
|
|
12200
|
-
const { dryRun, onConflict, prune } = options;
|
|
12201
|
-
|
|
12937
|
+
const { dryRun, onConflict, prune, expectNoChanges, expectedRemoteHash, hashProbe, release } = options;
|
|
12938
|
+
const plan = dryRun || expectNoChanges;
|
|
12939
|
+
const guard = expectedRemoteHash ? { expectedRemoteHash } : {};
|
|
12940
|
+
if (hashProbe && release === "none" && !plan && !prune) {
|
|
12941
|
+
const probe = await requestFpoEnsure(client, {
|
|
12942
|
+
name: (isPlainObject$1(fpo.product) ? fpo.product : {}).name,
|
|
12943
|
+
contentHash: await computeFpoContentHash(fpo),
|
|
12944
|
+
...guard,
|
|
12945
|
+
release
|
|
12946
|
+
});
|
|
12947
|
+
if (probe.result !== "definitionRequired") return probe;
|
|
12948
|
+
}
|
|
12949
|
+
const result = await requestFpoEnsure(client, {
|
|
12202
12950
|
fpo,
|
|
12203
|
-
...
|
|
12951
|
+
...plan ? { dryRun: true } : {},
|
|
12204
12952
|
...onConflict ? { onConflict } : {},
|
|
12205
|
-
...prune ? { prune: true } : {}
|
|
12953
|
+
...prune ? { prune: true } : {},
|
|
12954
|
+
...guard,
|
|
12955
|
+
...release ? { release } : {}
|
|
12206
12956
|
});
|
|
12957
|
+
if (result.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-FPO request");
|
|
12958
|
+
if (expectNoChanges && fpoPlanHasDrift(result)) throw new FpoDriftError(result);
|
|
12959
|
+
return result;
|
|
12207
12960
|
}
|
|
12208
12961
|
/**
|
|
12209
12962
|
* Reconstruct a self-contained FPO from the live product graph (resolved by
|
|
@@ -12378,8 +13131,8 @@ function defineSurface(input) {
|
|
|
12378
13131
|
if (typeof input.name !== "string" || input.name.length === 0) throw new Error("defineSurface requires a non-empty string \"name\"");
|
|
12379
13132
|
if (typeof input.type !== "string" || !SURFACE_DEFINITION_TYPES.has(input.type)) throw new Error(`defineSurface requires "type" to be one of: ${[...SURFACE_DEFINITION_TYPES].join(", ")}`);
|
|
12380
13133
|
if (input.behavior !== void 0 && !isPlainObject$1(input.behavior)) throw new Error("defineSurface \"behavior\" must be an object when provided");
|
|
12381
|
-
if (input.inbound !== void 0 && !isPlainObject$1(input.inbound)) throw new Error("defineSurface \"inbound\" must be an object when provided");
|
|
12382
|
-
if (input.outbound !== void 0 && !isPlainObject$1(input.outbound)) throw new Error("defineSurface \"outbound\" must be an object when provided");
|
|
13134
|
+
if (input.inbound !== void 0 && input.inbound !== null && !isPlainObject$1(input.inbound)) throw new Error("defineSurface \"inbound\" must be an object or null when provided");
|
|
13135
|
+
if (input.outbound !== void 0 && input.outbound !== null && !isPlainObject$1(input.outbound)) throw new Error("defineSurface \"outbound\" must be an object or null when provided");
|
|
12383
13136
|
if (input.status !== void 0 && ![
|
|
12384
13137
|
"draft",
|
|
12385
13138
|
"active",
|
|
@@ -12501,14 +13254,16 @@ async function ensureSurface(client, productId, definition, options = {}) {
|
|
|
12501
13254
|
const localHash = await computeSurfaceContentHash(definition);
|
|
12502
13255
|
const memoKey = `${productId} ${definition.name} ${localHash}`;
|
|
12503
13256
|
const contentHash = memo.get(memoKey) ?? localHash;
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
13257
|
+
if (!(definition.inbound !== void 0 || definition.outbound !== void 0)) {
|
|
13258
|
+
const probe = await request(client, productId, {
|
|
13259
|
+
name: definition.name,
|
|
13260
|
+
contentHash,
|
|
13261
|
+
...passthrough
|
|
13262
|
+
});
|
|
13263
|
+
if (probe.result !== "definitionRequired") {
|
|
13264
|
+
memoize(memo, memoKey, probe);
|
|
13265
|
+
return probe;
|
|
13266
|
+
}
|
|
12512
13267
|
}
|
|
12513
13268
|
const converged = await request(client, productId, {
|
|
12514
13269
|
name: definition.name,
|
|
@@ -12521,8 +13276,8 @@ async function ensureSurface(client, productId, definition, options = {}) {
|
|
|
12521
13276
|
}
|
|
12522
13277
|
/**
|
|
12523
13278
|
* Pull the canonical definition + provenance for a surface by name within a
|
|
12524
|
-
* product — the absorb-drift direction.
|
|
12525
|
-
*
|
|
13279
|
+
* product — the absorb-drift direction. Configured credentials come back as
|
|
13280
|
+
* `'<redacted>'`, which `ensure` accepts to keep the stored value.
|
|
12526
13281
|
*/
|
|
12527
13282
|
async function pullSurface(client, productId, name) {
|
|
12528
13283
|
return client.get(`/products/${encodeURIComponent(productId)}/surfaces/pull`, { name });
|
|
@@ -13237,6 +13992,25 @@ var Runtype = class {
|
|
|
13237
13992
|
return new ToolsNamespace(() => this.getClient());
|
|
13238
13993
|
}
|
|
13239
13994
|
/**
|
|
13995
|
+
* Question sets namespace - Question set config-as-code (define / ensure / pull)
|
|
13996
|
+
*
|
|
13997
|
+
* @example
|
|
13998
|
+
* ```typescript
|
|
13999
|
+
* import { defineQuestionSet, Runtype } from '@runtypelabs/sdk'
|
|
14000
|
+
*
|
|
14001
|
+
* const triage = defineQuestionSet({
|
|
14002
|
+
* slug: 'ticket-triage',
|
|
14003
|
+
* name: 'Ticket triage',
|
|
14004
|
+
* questions: { urgent: { type: 'boolean', instructions: 'Is the ticket urgent?' } },
|
|
14005
|
+
* })
|
|
14006
|
+
*
|
|
14007
|
+
* await Runtype.questionSets.ensure(triage, { release: 'publish' })
|
|
14008
|
+
* ```
|
|
14009
|
+
*/
|
|
14010
|
+
static get questionSets() {
|
|
14011
|
+
return new QuestionSetsNamespace(() => this.getClient());
|
|
14012
|
+
}
|
|
14013
|
+
/**
|
|
13240
14014
|
* Products namespace - Product config-as-code (define / ensure / pull)
|
|
13241
14015
|
*
|
|
13242
14016
|
* Converges the top-level product record (description, icon, spec). Nested
|
|
@@ -13525,7 +14299,7 @@ function transformQueryParams(params) {
|
|
|
13525
14299
|
}
|
|
13526
14300
|
//#endregion
|
|
13527
14301
|
//#region src/version.ts
|
|
13528
|
-
const SDK_VERSION = "10.
|
|
14302
|
+
const SDK_VERSION = "10.9.0";
|
|
13529
14303
|
/**
|
|
13530
14304
|
* Canonical client identity the API uses for attribution
|
|
13531
14305
|
* (`detectActorSource` in apps/api/src/lib/audit-log.ts keys `'sdk'` off the
|
|
@@ -15741,6 +16515,8 @@ exports.ClientTokenDriftError = ClientTokenDriftError;
|
|
|
15741
16515
|
exports.ClientTokenEnsureError = ClientTokenEnsureError;
|
|
15742
16516
|
exports.ClientTokensEndpoint = ClientTokensEndpoint;
|
|
15743
16517
|
exports.ClientTokensNamespace = ClientTokensNamespace;
|
|
16518
|
+
exports.CollectionDriftError = CollectionDriftError;
|
|
16519
|
+
exports.CollectionEnsureConflictError = CollectionEnsureConflictError;
|
|
15744
16520
|
exports.CollectionsEndpoint = CollectionsEndpoint;
|
|
15745
16521
|
exports.ContextTemplatesEndpoint = ContextTemplatesEndpoint;
|
|
15746
16522
|
exports.ConversationsEndpoint = ConversationsEndpoint;
|
|
@@ -15750,7 +16526,9 @@ exports.DEFAULT_STALL_STOP_AFTER = DEFAULT_STALL_STOP_AFTER;
|
|
|
15750
16526
|
exports.DispatchEndpoint = DispatchEndpoint;
|
|
15751
16527
|
exports.ENSURE_RELEASE_DEPLOY_CONFLICT_MESSAGE = ENSURE_RELEASE_DEPLOY_CONFLICT_MESSAGE;
|
|
15752
16528
|
exports.EvalBuilder = EvalBuilder;
|
|
16529
|
+
exports.EvalDriftError = EvalDriftError;
|
|
15753
16530
|
exports.EvalEndpoint = EvalEndpoint;
|
|
16531
|
+
exports.EvalEnsureConflictError = EvalEnsureConflictError;
|
|
15754
16532
|
exports.EvalRunner = EvalRunner;
|
|
15755
16533
|
exports.EvalSuitesNamespace = EvalSuitesNamespace;
|
|
15756
16534
|
exports.EvalsNamespace = EvalsNamespace;
|
|
@@ -15764,6 +16542,7 @@ exports.FlowStepsEndpoint = FlowStepsEndpoint;
|
|
|
15764
16542
|
exports.FlowVersionsEndpoint = FlowVersionsEndpoint;
|
|
15765
16543
|
exports.FlowsEndpoint = FlowsEndpoint;
|
|
15766
16544
|
exports.FlowsNamespace = FlowsNamespace;
|
|
16545
|
+
exports.FpoDriftError = FpoDriftError;
|
|
15767
16546
|
exports.IntegrationsEndpoint = IntegrationsEndpoint;
|
|
15768
16547
|
exports.LEDGER_ARTIFACT_LINE_PREFIX = LEDGER_ARTIFACT_LINE_PREFIX;
|
|
15769
16548
|
exports.LIVE_AGENT_ALIAS = LIVE_AGENT_ALIAS;
|
|
@@ -15775,6 +16554,9 @@ exports.ProductsNamespace = ProductsNamespace;
|
|
|
15775
16554
|
exports.PromptsEndpoint = PromptsEndpoint;
|
|
15776
16555
|
exports.PromptsNamespace = PromptsNamespace;
|
|
15777
16556
|
exports.ProviderKeysEndpoint = ProviderKeysEndpoint;
|
|
16557
|
+
exports.QuestionSetDriftError = QuestionSetDriftError;
|
|
16558
|
+
exports.QuestionSetEnsureConflictError = QuestionSetEnsureConflictError;
|
|
16559
|
+
exports.QuestionSetsNamespace = QuestionSetsNamespace;
|
|
15778
16560
|
exports.RUNTYPE_CLIENT_KIND = RUNTYPE_CLIENT_KIND;
|
|
15779
16561
|
exports.RecordsEndpoint = RecordsEndpoint;
|
|
15780
16562
|
exports.RunsNamespace = RunsNamespace;
|
|
@@ -15786,6 +16568,8 @@ exports.SDK_USER_AGENT = SDK_USER_AGENT;
|
|
|
15786
16568
|
exports.SDK_VERSION = SDK_VERSION;
|
|
15787
16569
|
exports.STEP_FIELD_REGISTRY = STEP_FIELD_REGISTRY;
|
|
15788
16570
|
exports.STEP_TYPE_TO_METHOD = STEP_TYPE_TO_METHOD;
|
|
16571
|
+
exports.ScheduleDriftError = ScheduleDriftError;
|
|
16572
|
+
exports.ScheduleEnsureConflictError = ScheduleEnsureConflictError;
|
|
15789
16573
|
exports.SchedulesEndpoint = SchedulesEndpoint;
|
|
15790
16574
|
exports.SecretsEndpoint = SecretsEndpoint;
|
|
15791
16575
|
exports.SkillDriftError = SkillDriftError;
|
|
@@ -15825,6 +16609,10 @@ exports.computeEvalContentHash = computeEvalContentHash;
|
|
|
15825
16609
|
exports.computeFlowContentHash = computeFlowContentHash;
|
|
15826
16610
|
exports.computeFpoContentHash = computeFpoContentHash;
|
|
15827
16611
|
exports.computeProductContentHash = computeProductContentHash;
|
|
16612
|
+
exports.computeQuestionSetContentHash = computeQuestionSetContentHash;
|
|
16613
|
+
exports.computeRecordCollectionContentHash = computeRecordCollectionContentHash;
|
|
16614
|
+
exports.computeScheduleContentHash = computeScheduleContentHash;
|
|
16615
|
+
exports.computeScheduleDefinitionHash = computeScheduleDefinitionHash;
|
|
15828
16616
|
exports.computeSkillContentHash = computeSkillContentHash;
|
|
15829
16617
|
exports.computeSurfaceContentHash = computeSurfaceContentHash;
|
|
15830
16618
|
exports.computeToolContentHash = computeToolContentHash;
|
|
@@ -15842,6 +16630,8 @@ exports.defineFlow = defineFlow;
|
|
|
15842
16630
|
exports.defineFpo = defineFpo;
|
|
15843
16631
|
exports.definePlaybook = definePlaybook;
|
|
15844
16632
|
exports.defineProduct = defineProduct;
|
|
16633
|
+
exports.defineQuestionSet = defineQuestionSet;
|
|
16634
|
+
exports.defineSchedule = defineSchedule;
|
|
15845
16635
|
exports.defineSkill = defineSkill;
|
|
15846
16636
|
exports.defineSurface = defineSurface;
|
|
15847
16637
|
exports.defineTool = defineTool;
|
|
@@ -15849,8 +16639,10 @@ exports.deployWorkflow = deployWorkflow;
|
|
|
15849
16639
|
exports.ensureDefaultWorkflowHooks = ensureDefaultWorkflowHooks;
|
|
15850
16640
|
exports.ensureEval = ensureEval;
|
|
15851
16641
|
exports.ensureFpo = ensureFpo;
|
|
16642
|
+
exports.ensureSchedule = ensureSchedule;
|
|
15852
16643
|
exports.evaluateGeneratedRuntimeToolProposal = evaluateGeneratedRuntimeToolProposal;
|
|
15853
16644
|
exports.extractDeclaredToolResultChars = extractDeclaredToolResultChars;
|
|
16645
|
+
exports.fpoPlanHasDrift = fpoPlanHasDrift;
|
|
15854
16646
|
exports.gameWorkflow = gameWorkflow;
|
|
15855
16647
|
exports.getDefaultPlanPath = getDefaultPlanPath;
|
|
15856
16648
|
exports.getLikelySupportingCandidatePaths = getLikelySupportingCandidatePaths;
|
|
@@ -15875,6 +16667,9 @@ exports.normalizeAgentDefinition = normalizeAgentDefinition;
|
|
|
15875
16667
|
exports.normalizeCandidatePath = normalizeCandidatePath;
|
|
15876
16668
|
exports.normalizeFpoDefinition = normalizeFpoDefinition;
|
|
15877
16669
|
exports.normalizeProductDefinition = normalizeProductDefinition;
|
|
16670
|
+
exports.normalizeQuestionSetDefinition = normalizeQuestionSetDefinition;
|
|
16671
|
+
exports.normalizeRecordCollectionDefinition = normalizeRecordCollectionDefinition;
|
|
16672
|
+
exports.normalizeScheduleContent = normalizeScheduleContent;
|
|
15878
16673
|
exports.normalizeSkillDefinition = normalizeSkillDefinition;
|
|
15879
16674
|
exports.normalizeSurfaceDefinition = normalizeSurfaceDefinition;
|
|
15880
16675
|
exports.normalizeToolDefinition = normalizeToolDefinition;
|
|
@@ -15890,6 +16685,7 @@ exports.promoteAgent = promoteAgent;
|
|
|
15890
16685
|
exports.promotionIdempotencyKey = promotionIdempotencyKey;
|
|
15891
16686
|
exports.pullEval = pullEval;
|
|
15892
16687
|
exports.pullFpo = pullFpo;
|
|
16688
|
+
exports.pullSchedule = pullSchedule;
|
|
15893
16689
|
exports.ranStep = ranStep;
|
|
15894
16690
|
exports.regex = regex;
|
|
15895
16691
|
exports.registerWorkflowHook = registerWorkflowHook;
|