@runtypelabs/sdk 10.8.1 → 10.11.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 +988 -215
- package/dist/index.d.cts +2061 -536
- package/dist/index.d.ts +2061 -536
- package/dist/index.mjs +967 -215
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -2080,21 +2080,6 @@ var AgentAliasNotFoundError = class extends Error {
|
|
|
2080
2080
|
this.agentId = body.agentId;
|
|
2081
2081
|
}
|
|
2082
2082
|
};
|
|
2083
|
-
/**
|
|
2084
|
-
* A new preview pointer refused by the active-preview quota (HTTP 429). Archive
|
|
2085
|
-
* a preview you no longer need — archiving frees the quota immediately — or wait
|
|
2086
|
-
* for one to expire. `live` is never counted.
|
|
2087
|
-
*/
|
|
2088
|
-
var AgentAliasPreviewLimitError = class extends Error {
|
|
2089
|
-
constructor(body) {
|
|
2090
|
-
super(body.error ?? `This ${body.scope} already has ${body.limit} active preview aliases, the maximum.`);
|
|
2091
|
-
this.code = "PREVIEW_ALIAS_LIMIT";
|
|
2092
|
-
this.name = "AgentAliasPreviewLimitError";
|
|
2093
|
-
this.scope = body.scope;
|
|
2094
|
-
this.limit = body.limit;
|
|
2095
|
-
this.active = body.active;
|
|
2096
|
-
}
|
|
2097
|
-
};
|
|
2098
2083
|
/** A version whose named references no longer resolve in this account (HTTP 422). */
|
|
2099
2084
|
var AgentAliasDependencyError = class extends Error {
|
|
2100
2085
|
constructor(body) {
|
|
@@ -2114,7 +2099,7 @@ function asRecord(value) {
|
|
|
2114
2099
|
* Reading the structured fields FIRST is what makes the typed alias errors fire
|
|
2115
2100
|
* for `createClient()` and the CLI, not just for `Runtype.agents`.
|
|
2116
2101
|
*/
|
|
2117
|
-
function parseRequestError$
|
|
2102
|
+
function parseRequestError$7(err) {
|
|
2118
2103
|
if (!(err instanceof Error)) return {
|
|
2119
2104
|
status: null,
|
|
2120
2105
|
body: null
|
|
@@ -2149,7 +2134,7 @@ function asString$1(value, fallback) {
|
|
|
2149
2134
|
* failure exactly as the transport threw it.
|
|
2150
2135
|
*/
|
|
2151
2136
|
function toAliasError(err, agentId, alias) {
|
|
2152
|
-
const { status, body } = parseRequestError$
|
|
2137
|
+
const { status, body } = parseRequestError$7(err);
|
|
2153
2138
|
if (status === null) return null;
|
|
2154
2139
|
const error = body && typeof body.error === "string" ? body.error : void 0;
|
|
2155
2140
|
if (status === 412 && body && typeof body.expected === "number") return new AgentAliasRevisionMismatchError({
|
|
@@ -2163,12 +2148,6 @@ function toAliasError(err, agentId, alias) {
|
|
|
2163
2148
|
alias: asString$1(body.alias, alias),
|
|
2164
2149
|
agentId: asString$1(body.agentId, agentId)
|
|
2165
2150
|
});
|
|
2166
|
-
if (status === 429 && body?.code === "PREVIEW_ALIAS_LIMIT") return new AgentAliasPreviewLimitError({
|
|
2167
|
-
...error !== void 0 ? { error } : {},
|
|
2168
|
-
scope: body.scope === "organization" ? "organization" : "agent",
|
|
2169
|
-
limit: typeof body.limit === "number" ? body.limit : 0,
|
|
2170
|
-
active: typeof body.active === "number" ? body.active : 0
|
|
2171
|
-
});
|
|
2172
2151
|
if (status === 422 && body?.code === "alias_dependency_unresolved") return new AgentAliasDependencyError({
|
|
2173
2152
|
...error !== void 0 ? { error } : {},
|
|
2174
2153
|
refs: Array.isArray(body.refs) ? body.refs.filter((ref) => typeof ref === "string") : []
|
|
@@ -2179,8 +2158,7 @@ const TYPED_ALIAS_ERRORS = [
|
|
|
2179
2158
|
AgentAliasRevisionMismatchError,
|
|
2180
2159
|
AgentAliasRevisionRequiredError,
|
|
2181
2160
|
AgentAliasNotFoundError,
|
|
2182
|
-
AgentAliasDependencyError
|
|
2183
|
-
AgentAliasPreviewLimitError
|
|
2161
|
+
AgentAliasDependencyError
|
|
2184
2162
|
];
|
|
2185
2163
|
/** The refusal code, when this is one of the typed alias errors. */
|
|
2186
2164
|
function agentAliasErrorCode(err) {
|
|
@@ -2339,6 +2317,174 @@ var AgentDeploymentsNamespace = class {
|
|
|
2339
2317
|
}
|
|
2340
2318
|
};
|
|
2341
2319
|
//#endregion
|
|
2320
|
+
//#region src/content-hash.ts
|
|
2321
|
+
/**
|
|
2322
|
+
* Canonical content-hash primitives shared by the `*.ensure()` config-as-code
|
|
2323
|
+
* surfaces (products, tools, skills, surfaces, products-ensure-fpo). Each of
|
|
2324
|
+
* those modules computes a SHA-256 content hash for its hash-first `/ensure`
|
|
2325
|
+
* wire protocol (see docs/adr/0003-agent-config-as-code-ensure.md) — the hash
|
|
2326
|
+
* output for any given definition is a WIRE CONTRACT with the server, so this
|
|
2327
|
+
* module must stay dependency-free (the SDK is published to npm and must not
|
|
2328
|
+
* import `@runtypelabs/shared`, a private package) and must not change its
|
|
2329
|
+
* normalization behavior without a matching server-side change.
|
|
2330
|
+
*
|
|
2331
|
+
* `isPlainObject` and `normalizeValue` here are mirrored from
|
|
2332
|
+
* `packages/shared/src/utils/*-content-hash.ts`. `normalizeValue` sorts object
|
|
2333
|
+
* keys and drops `null`/`undefined` entries recursively — this is the
|
|
2334
|
+
* canonical normalization used by products, tools, skills, surfaces, and the
|
|
2335
|
+
* whole-FPO hash.
|
|
2336
|
+
*
|
|
2337
|
+
* NOT every `*-ensure.ts` module uses this normalization: `flows-ensure.ts`
|
|
2338
|
+
* (`normalizeConfigForHash`/`normalizeStepForHash`) and `evals-ensure.ts`
|
|
2339
|
+
* (`normalizeForHash`) keep `null` values (they drop only `undefined`) and,
|
|
2340
|
+
* in the flow case, use a shallower array-recursion rule. Those are real
|
|
2341
|
+
* semantic divergences from this canonical normalizer, not incidental
|
|
2342
|
+
* duplication — changing either to reuse `normalizeValue` would alter the
|
|
2343
|
+
* content hash for definitions containing explicit `null`s and break the
|
|
2344
|
+
* wire contract with already-converged flows/evals. They stay local by
|
|
2345
|
+
* design; see the divergence comment at each site.
|
|
2346
|
+
*/
|
|
2347
|
+
function isPlainObject$1(value) {
|
|
2348
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2349
|
+
}
|
|
2350
|
+
/**
|
|
2351
|
+
* Recursively normalizes a value for canonical hashing: object keys are
|
|
2352
|
+
* sorted, and `null`/`undefined` entries are dropped. Arrays preserve order
|
|
2353
|
+
* and have each item normalized in turn.
|
|
2354
|
+
*/
|
|
2355
|
+
function normalizeValue$1(value) {
|
|
2356
|
+
if (Array.isArray(value)) return value.map((item) => normalizeValue$1(item));
|
|
2357
|
+
if (isPlainObject$1(value)) {
|
|
2358
|
+
const normalized = {};
|
|
2359
|
+
for (const key of Object.keys(value).sort()) {
|
|
2360
|
+
const entry = value[key];
|
|
2361
|
+
if (entry === void 0 || entry === null) continue;
|
|
2362
|
+
normalized[key] = normalizeValue$1(entry);
|
|
2363
|
+
}
|
|
2364
|
+
return normalized;
|
|
2365
|
+
}
|
|
2366
|
+
return value;
|
|
2367
|
+
}
|
|
2368
|
+
/** SHA-256 (hex) over an already-serialized (typically `JSON.stringify`'d) string. */
|
|
2369
|
+
async function sha256Hex(serialized) {
|
|
2370
|
+
const encoded = new TextEncoder().encode(serialized);
|
|
2371
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
|
|
2372
|
+
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2373
|
+
}
|
|
2374
|
+
//#endregion
|
|
2375
|
+
//#region src/collections-ensure.ts
|
|
2376
|
+
/** Canonical normalized form of a collection definition (slug excluded — identity). */
|
|
2377
|
+
function normalizeRecordCollectionDefinition(definition) {
|
|
2378
|
+
return {
|
|
2379
|
+
displayName: definition.displayName,
|
|
2380
|
+
...definition.description ? { description: definition.description } : {},
|
|
2381
|
+
...definition.icon ? { icon: definition.icon } : {},
|
|
2382
|
+
...isPlainObject$1(definition.schema) ? { schema: normalizeValue$1(definition.schema) } : {},
|
|
2383
|
+
validationMode: definition.validationMode || "off"
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
/** SHA-256 (hex) over the canonical normalized collection definition. */
|
|
2387
|
+
async function computeRecordCollectionContentHash(definition) {
|
|
2388
|
+
return sha256Hex(JSON.stringify(normalizeRecordCollectionDefinition(definition)));
|
|
2389
|
+
}
|
|
2390
|
+
/** A 409 from the ensure protocol (external_modification or remote_changed). */
|
|
2391
|
+
var CollectionEnsureConflictError = class extends Error {
|
|
2392
|
+
constructor(body) {
|
|
2393
|
+
super(body.error ?? `Collection ensure conflict: ${body.code}`);
|
|
2394
|
+
this.name = "CollectionEnsureConflictError";
|
|
2395
|
+
this.code = body.code;
|
|
2396
|
+
this.lastModifiedSource = body.lastModifiedSource;
|
|
2397
|
+
this.modifiedAt = body.modifiedAt;
|
|
2398
|
+
this.currentHash = body.currentHash;
|
|
2399
|
+
}
|
|
2400
|
+
};
|
|
2401
|
+
/** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
|
|
2402
|
+
var CollectionDriftError = class extends Error {
|
|
2403
|
+
constructor(slug, plan) {
|
|
2404
|
+
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.`);
|
|
2405
|
+
this.name = "CollectionDriftError";
|
|
2406
|
+
this.plan = plan;
|
|
2407
|
+
}
|
|
2408
|
+
};
|
|
2409
|
+
function toConflictError$8(err) {
|
|
2410
|
+
if (!(err instanceof Error)) return null;
|
|
2411
|
+
const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
|
|
2412
|
+
if (!match || match[1] !== "409") return null;
|
|
2413
|
+
let body;
|
|
2414
|
+
try {
|
|
2415
|
+
body = JSON.parse(match[2]);
|
|
2416
|
+
} catch {
|
|
2417
|
+
return null;
|
|
2418
|
+
}
|
|
2419
|
+
if (!isPlainObject$1(body)) return null;
|
|
2420
|
+
if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
|
|
2421
|
+
return new CollectionEnsureConflictError(body);
|
|
2422
|
+
}
|
|
2423
|
+
/** Per-transport memo of `{ slug + localHash → server contentHash }`, as in tools.ensure. */
|
|
2424
|
+
const serverHashMemo$9 = /* @__PURE__ */ new WeakMap();
|
|
2425
|
+
function memoFor$9(transport) {
|
|
2426
|
+
let memo = serverHashMemo$9.get(transport);
|
|
2427
|
+
if (!memo) {
|
|
2428
|
+
memo = /* @__PURE__ */ new Map();
|
|
2429
|
+
serverHashMemo$9.set(transport, memo);
|
|
2430
|
+
}
|
|
2431
|
+
return memo;
|
|
2432
|
+
}
|
|
2433
|
+
async function request$7(transport, body) {
|
|
2434
|
+
try {
|
|
2435
|
+
return await transport.post("/collections/ensure", body);
|
|
2436
|
+
} catch (err) {
|
|
2437
|
+
throw toConflictError$8(err) ?? err;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
/**
|
|
2441
|
+
* Idempotently converge a collection definition. Probes with a content hash and
|
|
2442
|
+
* only ships the full definition when the server reports a miss. Never deletes
|
|
2443
|
+
* a collection and never touches records.
|
|
2444
|
+
*/
|
|
2445
|
+
async function ensureCollection(transport, definition, options = {}) {
|
|
2446
|
+
const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
|
|
2447
|
+
const passthrough = {
|
|
2448
|
+
...onConflict ? { onConflict } : {},
|
|
2449
|
+
...expectedRemoteHash ? { expectedRemoteHash } : {}
|
|
2450
|
+
};
|
|
2451
|
+
if (dryRun || expectNoChanges) {
|
|
2452
|
+
const plan = await request$7(transport, {
|
|
2453
|
+
slug: definition.slug,
|
|
2454
|
+
definition,
|
|
2455
|
+
dryRun: true,
|
|
2456
|
+
...passthrough
|
|
2457
|
+
});
|
|
2458
|
+
if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
|
|
2459
|
+
if (expectNoChanges && plan.changes !== "none") throw new CollectionDriftError(definition.slug, plan);
|
|
2460
|
+
return plan;
|
|
2461
|
+
}
|
|
2462
|
+
const memo = memoFor$9(transport);
|
|
2463
|
+
const localHash = await computeRecordCollectionContentHash(definition);
|
|
2464
|
+
const memoKey = `${definition.slug} ${localHash}`;
|
|
2465
|
+
const probe = await request$7(transport, {
|
|
2466
|
+
slug: definition.slug,
|
|
2467
|
+
contentHash: memo.get(memoKey) ?? localHash,
|
|
2468
|
+
...passthrough
|
|
2469
|
+
});
|
|
2470
|
+
if (probe.result !== "definitionRequired") {
|
|
2471
|
+
if (probe.result !== "plan") memo.set(memoKey, probe.contentHash);
|
|
2472
|
+
return probe;
|
|
2473
|
+
}
|
|
2474
|
+
const converged = await request$7(transport, {
|
|
2475
|
+
slug: definition.slug,
|
|
2476
|
+
definition,
|
|
2477
|
+
...passthrough
|
|
2478
|
+
});
|
|
2479
|
+
if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
|
|
2480
|
+
if (converged.result !== "plan") memo.set(memoKey, converged.contentHash);
|
|
2481
|
+
return converged;
|
|
2482
|
+
}
|
|
2483
|
+
/** Pull the canonical definition and provenance for a collection by slug. */
|
|
2484
|
+
async function pullCollection(transport, slug) {
|
|
2485
|
+
return transport.get(`/collections/${encodeURIComponent(slug)}/pull`);
|
|
2486
|
+
}
|
|
2487
|
+
//#endregion
|
|
2342
2488
|
//#region src/generated-tool-gate.ts
|
|
2343
2489
|
const TOOL_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]{1,63}$/;
|
|
2344
2490
|
const DEFAULT_MAX_CODE_LENGTH = 12e3;
|
|
@@ -2667,6 +2813,239 @@ function normalizeDispatchRequest(request) {
|
|
|
2667
2813
|
};
|
|
2668
2814
|
}
|
|
2669
2815
|
//#endregion
|
|
2816
|
+
//#region src/schedules-ensure.ts
|
|
2817
|
+
function nonEmptyObject(value) {
|
|
2818
|
+
if (!isPlainObject$1(value)) return void 0;
|
|
2819
|
+
const normalized = normalizeValue$1(value);
|
|
2820
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
2821
|
+
}
|
|
2822
|
+
function nonEmptyString(value) {
|
|
2823
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
2824
|
+
}
|
|
2825
|
+
function canonicalRunAt(value) {
|
|
2826
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
2827
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
2828
|
+
return Number.isNaN(date.getTime()) ? String(value) : date.toISOString();
|
|
2829
|
+
}
|
|
2830
|
+
/** Canonical normalized schedule content (name and target excluded — identity). */
|
|
2831
|
+
function normalizeScheduleContent(definition) {
|
|
2832
|
+
const cron = nonEmptyString(definition.cron);
|
|
2833
|
+
const timezone = nonEmptyString(definition.timezone);
|
|
2834
|
+
const runAt = canonicalRunAt(definition.runAt);
|
|
2835
|
+
const recordIds = Array.isArray(definition.recordIds) && definition.recordIds.length > 0 ? [...definition.recordIds] : void 0;
|
|
2836
|
+
const recordType = nonEmptyString(definition.recordType);
|
|
2837
|
+
const recordFilter = nonEmptyObject(definition.recordFilter);
|
|
2838
|
+
const messages = Array.isArray(definition.messages) && definition.messages.length > 0 ? normalizeValue$1(definition.messages) : void 0;
|
|
2839
|
+
const inputs = nonEmptyObject(definition.inputs);
|
|
2840
|
+
const executionOptions = nonEmptyObject(definition.executionOptions);
|
|
2841
|
+
return {
|
|
2842
|
+
triggerType: definition.triggerType === "one_time" ? "one_time" : "recurring",
|
|
2843
|
+
...cron !== void 0 ? { cron } : {},
|
|
2844
|
+
...timezone !== void 0 ? { timezone } : {},
|
|
2845
|
+
...runAt !== void 0 ? { runAt } : {},
|
|
2846
|
+
...recordIds !== void 0 ? { recordIds } : {},
|
|
2847
|
+
...recordType !== void 0 ? { recordType } : {},
|
|
2848
|
+
...recordFilter !== void 0 ? { recordFilter } : {},
|
|
2849
|
+
...messages !== void 0 ? { messages } : {},
|
|
2850
|
+
...inputs !== void 0 ? { inputs } : {},
|
|
2851
|
+
...executionOptions !== void 0 ? { executionOptions } : {},
|
|
2852
|
+
...typeof definition.enabled === "boolean" ? { enabled: definition.enabled } : {}
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
/** SHA-256 (hex) over canonical schedule content, in the flat content shape. */
|
|
2856
|
+
async function computeScheduleContentHash(definition) {
|
|
2857
|
+
return sha256Hex(JSON.stringify(normalizeScheduleContent(definition)));
|
|
2858
|
+
}
|
|
2859
|
+
/** The content hash of a `defineSchedule` definition. */
|
|
2860
|
+
async function computeScheduleDefinitionHash(definition) {
|
|
2861
|
+
return computeScheduleContentHash({
|
|
2862
|
+
triggerType: definition.trigger.type,
|
|
2863
|
+
cron: definition.trigger.cron,
|
|
2864
|
+
timezone: definition.trigger.timezone,
|
|
2865
|
+
runAt: definition.trigger.runAt,
|
|
2866
|
+
recordIds: definition.recordIds,
|
|
2867
|
+
recordType: definition.recordType,
|
|
2868
|
+
recordFilter: definition.recordFilter,
|
|
2869
|
+
messages: definition.messages,
|
|
2870
|
+
inputs: definition.inputs,
|
|
2871
|
+
executionOptions: definition.executionOptions,
|
|
2872
|
+
enabled: definition.enabled
|
|
2873
|
+
});
|
|
2874
|
+
}
|
|
2875
|
+
const TARGET_KEYS = [
|
|
2876
|
+
"flowId",
|
|
2877
|
+
"agentId",
|
|
2878
|
+
"flowName",
|
|
2879
|
+
"agentName"
|
|
2880
|
+
];
|
|
2881
|
+
const DEFINE_SCHEDULE_KEYS = /* @__PURE__ */ new Set([
|
|
2882
|
+
"name",
|
|
2883
|
+
"target",
|
|
2884
|
+
"trigger",
|
|
2885
|
+
"recordIds",
|
|
2886
|
+
"recordType",
|
|
2887
|
+
"recordFilter",
|
|
2888
|
+
"messages",
|
|
2889
|
+
"inputs",
|
|
2890
|
+
"executionOptions",
|
|
2891
|
+
"enabled"
|
|
2892
|
+
]);
|
|
2893
|
+
/**
|
|
2894
|
+
* Pure-local declarative constructor for a standalone schedule. No I/O.
|
|
2895
|
+
* Validates structure; cron, timezone, record filter and plan limits are
|
|
2896
|
+
* checked server-side at ensure time.
|
|
2897
|
+
*
|
|
2898
|
+
* @example
|
|
2899
|
+
* ```typescript
|
|
2900
|
+
* const nightly = defineSchedule({
|
|
2901
|
+
* name: 'Nightly digest',
|
|
2902
|
+
* target: { flowName: 'Digest' },
|
|
2903
|
+
* trigger: { type: 'recurring', cron: '0 2 * * *', timezone: 'UTC' },
|
|
2904
|
+
* })
|
|
2905
|
+
* ```
|
|
2906
|
+
*/
|
|
2907
|
+
function defineSchedule(input) {
|
|
2908
|
+
if (!input || typeof input !== "object") throw new Error("defineSchedule requires a definition object");
|
|
2909
|
+
if (typeof input.name !== "string" || input.name.length === 0) throw new Error("defineSchedule requires a non-empty string \"name\"");
|
|
2910
|
+
if (!isPlainObject$1(input.target)) throw new Error("defineSchedule requires a \"target\" object");
|
|
2911
|
+
const target = input.target;
|
|
2912
|
+
const setKeys = TARGET_KEYS.filter((key) => target[key] !== void 0);
|
|
2913
|
+
const extraKeys = Object.keys(target).filter((key) => !TARGET_KEYS.includes(key));
|
|
2914
|
+
if (setKeys.length !== 1 || extraKeys.length > 0) throw new Error("defineSchedule \"target\" needs exactly one of flowId, agentId, flowName, agentName");
|
|
2915
|
+
const trigger = input.trigger;
|
|
2916
|
+
if (!isPlainObject$1(trigger) || trigger.type !== "recurring" && trigger.type !== "one_time") throw new Error("defineSchedule requires \"trigger.type\" to be \"recurring\" or \"one_time\"");
|
|
2917
|
+
if (trigger.type === "recurring" && (!trigger.cron || !trigger.timezone)) throw new Error("defineSchedule: recurring triggers require \"cron\" and \"timezone\"");
|
|
2918
|
+
if (trigger.type === "one_time" && !trigger.runAt) throw new Error("defineSchedule: one_time triggers require \"runAt\"");
|
|
2919
|
+
const unknownKeys = Object.keys(input).filter((key) => !DEFINE_SCHEDULE_KEYS.has(key));
|
|
2920
|
+
if (unknownKeys.length > 0) throw new Error(`defineSchedule: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are ${[...DEFINE_SCHEDULE_KEYS].join(", ")}.`);
|
|
2921
|
+
return { ...input };
|
|
2922
|
+
}
|
|
2923
|
+
/** A 409 from the ensure protocol (external_modification or remote_changed). */
|
|
2924
|
+
var ScheduleEnsureConflictError = class extends Error {
|
|
2925
|
+
constructor(body) {
|
|
2926
|
+
super(body.error ?? `Schedule ensure conflict: ${body.code}`);
|
|
2927
|
+
this.name = "ScheduleEnsureConflictError";
|
|
2928
|
+
this.code = body.code;
|
|
2929
|
+
this.lastModifiedSource = body.lastModifiedSource;
|
|
2930
|
+
this.modifiedAt = body.modifiedAt;
|
|
2931
|
+
this.currentHash = body.currentHash;
|
|
2932
|
+
}
|
|
2933
|
+
};
|
|
2934
|
+
/** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
|
|
2935
|
+
var ScheduleDriftError = class extends Error {
|
|
2936
|
+
constructor(plan) {
|
|
2937
|
+
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.`);
|
|
2938
|
+
this.name = "ScheduleDriftError";
|
|
2939
|
+
this.plan = plan;
|
|
2940
|
+
}
|
|
2941
|
+
};
|
|
2942
|
+
function errorStatusAndBody(err) {
|
|
2943
|
+
if (!(err instanceof Error)) return {
|
|
2944
|
+
status: null,
|
|
2945
|
+
body: null
|
|
2946
|
+
};
|
|
2947
|
+
const typed = err;
|
|
2948
|
+
if (typeof typed.statusCode === "number") return {
|
|
2949
|
+
status: typed.statusCode,
|
|
2950
|
+
body: typed.data
|
|
2951
|
+
};
|
|
2952
|
+
const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
|
|
2953
|
+
if (!match) return {
|
|
2954
|
+
status: null,
|
|
2955
|
+
body: null
|
|
2956
|
+
};
|
|
2957
|
+
try {
|
|
2958
|
+
return {
|
|
2959
|
+
status: Number(match[1]),
|
|
2960
|
+
body: JSON.parse(match[2])
|
|
2961
|
+
};
|
|
2962
|
+
} catch {
|
|
2963
|
+
return {
|
|
2964
|
+
status: Number(match[1]),
|
|
2965
|
+
body: null
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
function toConflictError$7(err) {
|
|
2970
|
+
const { status, body } = errorStatusAndBody(err);
|
|
2971
|
+
if (status !== 409 || !isPlainObject$1(body)) return null;
|
|
2972
|
+
if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
|
|
2973
|
+
return new ScheduleEnsureConflictError(body);
|
|
2974
|
+
}
|
|
2975
|
+
const serverHashMemo$8 = /* @__PURE__ */ new WeakMap();
|
|
2976
|
+
function memoFor$8(client) {
|
|
2977
|
+
let memo = serverHashMemo$8.get(client);
|
|
2978
|
+
if (!memo) {
|
|
2979
|
+
memo = /* @__PURE__ */ new Map();
|
|
2980
|
+
serverHashMemo$8.set(client, memo);
|
|
2981
|
+
}
|
|
2982
|
+
return memo;
|
|
2983
|
+
}
|
|
2984
|
+
async function request$6(client, body) {
|
|
2985
|
+
try {
|
|
2986
|
+
return await client.post("/schedules/ensure", body);
|
|
2987
|
+
} catch (err) {
|
|
2988
|
+
const conflict = toConflictError$7(err);
|
|
2989
|
+
if (conflict) throw conflict;
|
|
2990
|
+
throw err;
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
/**
|
|
2994
|
+
* Idempotently converge a standalone schedule. Hash-first: probes with a
|
|
2995
|
+
* content hash and ships the full definition only on a miss. Never deletes.
|
|
2996
|
+
*/
|
|
2997
|
+
async function ensureSchedule(client, definition, options = {}) {
|
|
2998
|
+
const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
|
|
2999
|
+
const identity = {
|
|
3000
|
+
name: definition.name,
|
|
3001
|
+
target: definition.target
|
|
3002
|
+
};
|
|
3003
|
+
const passthrough = {
|
|
3004
|
+
...onConflict ? { onConflict } : {},
|
|
3005
|
+
...expectedRemoteHash ? { expectedRemoteHash } : {}
|
|
3006
|
+
};
|
|
3007
|
+
if (dryRun || expectNoChanges) {
|
|
3008
|
+
const plan = await request$6(client, {
|
|
3009
|
+
...identity,
|
|
3010
|
+
definition,
|
|
3011
|
+
dryRun: true,
|
|
3012
|
+
...passthrough
|
|
3013
|
+
});
|
|
3014
|
+
if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
|
|
3015
|
+
if (expectNoChanges && plan.changes !== "none") throw new ScheduleDriftError(plan);
|
|
3016
|
+
return plan;
|
|
3017
|
+
}
|
|
3018
|
+
const memo = memoFor$8(client);
|
|
3019
|
+
const localHash = await computeScheduleDefinitionHash(definition);
|
|
3020
|
+
const memoKey = `${JSON.stringify(identity)} ${localHash}`;
|
|
3021
|
+
if (!expectedRemoteHash) {
|
|
3022
|
+
const probe = await request$6(client, {
|
|
3023
|
+
...identity,
|
|
3024
|
+
contentHash: memo.get(memoKey) ?? localHash,
|
|
3025
|
+
...passthrough
|
|
3026
|
+
});
|
|
3027
|
+
if (probe.result !== "definitionRequired") {
|
|
3028
|
+
if (probe.result !== "plan") memo.set(memoKey, probe.contentHash);
|
|
3029
|
+
return probe;
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
const converged = await request$6(client, {
|
|
3033
|
+
...identity,
|
|
3034
|
+
definition,
|
|
3035
|
+
...passthrough
|
|
3036
|
+
});
|
|
3037
|
+
if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
|
|
3038
|
+
if (converged.result !== "plan") memo.set(memoKey, converged.contentHash);
|
|
3039
|
+
return converged;
|
|
3040
|
+
}
|
|
3041
|
+
/** Pull the canonical definition + provenance for a schedule by name and target. */
|
|
3042
|
+
async function pullSchedule(client, name, target) {
|
|
3043
|
+
return client.get("/schedules/pull", {
|
|
3044
|
+
name,
|
|
3045
|
+
...target
|
|
3046
|
+
});
|
|
3047
|
+
}
|
|
3048
|
+
//#endregion
|
|
2670
3049
|
//#region src/offload-markers.ts
|
|
2671
3050
|
const LEDGER_ARTIFACT_LINE_PREFIX = "Ledger artifact: ";
|
|
2672
3051
|
const SPILL_MARKER_PATTERN = /\[Output saved to (art_[A-Za-z0-9_-]+) \(([\d,]+) chars?\); read_offloaded_output to view\]/;
|
|
@@ -4640,6 +5019,28 @@ var CollectionsEndpoint = class {
|
|
|
4640
5019
|
async typegen() {
|
|
4641
5020
|
return this.client.get("/collections/types.d.ts");
|
|
4642
5021
|
}
|
|
5022
|
+
/**
|
|
5023
|
+
* Idempotently converge a repo-declared collection (display fields, schema,
|
|
5024
|
+
* validationMode) by slug. Hash-first; `dryRun` / `expectNoChanges` plan
|
|
5025
|
+
* without writing. Never deletes a collection or touches records.
|
|
5026
|
+
*
|
|
5027
|
+
* @example
|
|
5028
|
+
* ```typescript
|
|
5029
|
+
* await client.collections.ensure({
|
|
5030
|
+
* slug: 'customers',
|
|
5031
|
+
* displayName: 'Customers',
|
|
5032
|
+
* schema: { type: 'object', properties: { status: { type: 'string' } } },
|
|
5033
|
+
* validationMode: 'warn',
|
|
5034
|
+
* })
|
|
5035
|
+
* ```
|
|
5036
|
+
*/
|
|
5037
|
+
async ensure(definition, options) {
|
|
5038
|
+
return ensureCollection(this.client, definition, options);
|
|
5039
|
+
}
|
|
5040
|
+
/** Pull the canonical definition and provenance for a collection — the absorb-drift direction. */
|
|
5041
|
+
async pull(slug) {
|
|
5042
|
+
return pullCollection(this.client, slug);
|
|
5043
|
+
}
|
|
4643
5044
|
};
|
|
4644
5045
|
/**
|
|
4645
5046
|
* Agent API key REQUESTS — a device-authorization-style grant.
|
|
@@ -8172,6 +8573,21 @@ var SchedulesEndpoint = class {
|
|
|
8172
8573
|
return this.client.post("/schedules", data);
|
|
8173
8574
|
}
|
|
8174
8575
|
/**
|
|
8576
|
+
* Idempotently converge a `defineSchedule` definition on a standalone flow or
|
|
8577
|
+
* agent. Identity is name + target; the steady state is one hash probe. Never
|
|
8578
|
+
* deletes, and an omitted `enabled` leaves a paused schedule paused.
|
|
8579
|
+
*/
|
|
8580
|
+
async ensure(definition, options = {}) {
|
|
8581
|
+
return ensureSchedule(this.client, definition, options);
|
|
8582
|
+
}
|
|
8583
|
+
/**
|
|
8584
|
+
* Pull the canonical definition + provenance for a schedule by name and
|
|
8585
|
+
* target — the absorb-drift direction of `ensure`.
|
|
8586
|
+
*/
|
|
8587
|
+
async pull(name, target) {
|
|
8588
|
+
return pullSchedule(this.client, name, target);
|
|
8589
|
+
}
|
|
8590
|
+
/**
|
|
8175
8591
|
* Update a schedule (partial update supported).
|
|
8176
8592
|
*/
|
|
8177
8593
|
async update(id, data) {
|
|
@@ -8667,61 +9083,6 @@ async function followDetachedResume(client, ack, after, carried = {}) {
|
|
|
8667
9083
|
};
|
|
8668
9084
|
}
|
|
8669
9085
|
//#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
9086
|
//#region src/evals-ensure.ts
|
|
8726
9087
|
const CHECK_GRADER_KINDS = /* @__PURE__ */ new Set([
|
|
8727
9088
|
"contains",
|
|
@@ -9115,6 +9476,25 @@ async function computeEvalContentHash(definition) {
|
|
|
9115
9476
|
};
|
|
9116
9477
|
return sha256Hex(JSON.stringify(canonical));
|
|
9117
9478
|
}
|
|
9479
|
+
/** A 409 from the eval ensure protocol (external_modification or remote_changed). */
|
|
9480
|
+
var EvalEnsureConflictError = class extends Error {
|
|
9481
|
+
constructor(body) {
|
|
9482
|
+
super(body.error ?? `Eval ensure conflict: ${body.code}`);
|
|
9483
|
+
this.name = "EvalEnsureConflictError";
|
|
9484
|
+
this.code = body.code;
|
|
9485
|
+
this.lastModifiedSource = body.lastModifiedSource;
|
|
9486
|
+
this.modifiedAt = body.modifiedAt;
|
|
9487
|
+
this.currentHash = body.currentHash;
|
|
9488
|
+
}
|
|
9489
|
+
};
|
|
9490
|
+
/** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
|
|
9491
|
+
var EvalDriftError = class extends Error {
|
|
9492
|
+
constructor(plan) {
|
|
9493
|
+
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.`);
|
|
9494
|
+
this.name = "EvalDriftError";
|
|
9495
|
+
this.plan = plan;
|
|
9496
|
+
}
|
|
9497
|
+
};
|
|
9118
9498
|
/**
|
|
9119
9499
|
* Per-client memo of `{ name + localHash → server contentHash }` so a hot
|
|
9120
9500
|
* process pays the full-definition round trip once per boot (steady state
|
|
@@ -9122,44 +9502,75 @@ async function computeEvalContentHash(definition) {
|
|
|
9122
9502
|
* hash, so a definition that changes within the process can never false-match a
|
|
9123
9503
|
* stale server hash. Mirrors the flow ensure memo.
|
|
9124
9504
|
*/
|
|
9125
|
-
const serverHashMemo$
|
|
9126
|
-
function memoFor$
|
|
9127
|
-
let memo = serverHashMemo$
|
|
9505
|
+
const serverHashMemo$7 = /* @__PURE__ */ new WeakMap();
|
|
9506
|
+
function memoFor$7(client) {
|
|
9507
|
+
let memo = serverHashMemo$7.get(client);
|
|
9128
9508
|
if (!memo) {
|
|
9129
9509
|
memo = /* @__PURE__ */ new Map();
|
|
9130
|
-
serverHashMemo$
|
|
9510
|
+
serverHashMemo$7.set(client, memo);
|
|
9131
9511
|
}
|
|
9132
9512
|
return memo;
|
|
9133
9513
|
}
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
9514
|
+
function toEvalConflictError(err) {
|
|
9515
|
+
if (!(err instanceof Error)) return null;
|
|
9516
|
+
const match = err.message.match(/^API request failed: 409 .*? - ([\s\S]*)$/);
|
|
9517
|
+
if (!match) return null;
|
|
9518
|
+
let body;
|
|
9519
|
+
try {
|
|
9520
|
+
body = JSON.parse(match[1]);
|
|
9521
|
+
} catch {
|
|
9522
|
+
return null;
|
|
9523
|
+
}
|
|
9524
|
+
if (!isPlainObject$1(body)) return null;
|
|
9525
|
+
if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
|
|
9526
|
+
return new EvalEnsureConflictError(body);
|
|
9527
|
+
}
|
|
9528
|
+
async function postEvalEnsure(client, body) {
|
|
9529
|
+
try {
|
|
9530
|
+
return await client.post("/eval/ensure", body);
|
|
9531
|
+
} catch (err) {
|
|
9532
|
+
throw toEvalConflictError(err) ?? err;
|
|
9533
|
+
}
|
|
9534
|
+
}
|
|
9535
|
+
async function ensureEval(client, definition, options = {}) {
|
|
9145
9536
|
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
|
|
9537
|
+
const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
|
|
9538
|
+
const passthrough = {
|
|
9539
|
+
...onConflict ? { onConflict } : {},
|
|
9540
|
+
...expectedRemoteHash ? { expectedRemoteHash } : {}
|
|
9541
|
+
};
|
|
9542
|
+
if (dryRun || expectNoChanges) {
|
|
9543
|
+
const plan = await postEvalEnsure(client, {
|
|
9544
|
+
name: definition.name,
|
|
9545
|
+
definition,
|
|
9546
|
+
dryRun: true,
|
|
9547
|
+
...passthrough
|
|
9548
|
+
});
|
|
9549
|
+
if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
|
|
9550
|
+
if (expectNoChanges && plan.changes !== "none") throw new EvalDriftError(plan);
|
|
9551
|
+
return plan;
|
|
9552
|
+
}
|
|
9553
|
+
const memo = memoFor$7(client);
|
|
9147
9554
|
const localHash = await computeEvalContentHash(definition);
|
|
9148
9555
|
const memoKey = `${definition.name} ${localHash}`;
|
|
9149
9556
|
const contentHash = memo.get(memoKey) ?? localHash;
|
|
9150
|
-
const probe = await client
|
|
9557
|
+
const probe = await postEvalEnsure(client, {
|
|
9151
9558
|
name: definition.name,
|
|
9152
|
-
contentHash
|
|
9559
|
+
contentHash,
|
|
9560
|
+
...passthrough
|
|
9153
9561
|
});
|
|
9562
|
+
if (probe.result === "plan") throw new Error("Server returned a plan for a non-dryRun request");
|
|
9154
9563
|
if (probe.result !== "definitionRequired") {
|
|
9155
9564
|
memo.set(memoKey, probe.contentHash);
|
|
9156
9565
|
return probe;
|
|
9157
9566
|
}
|
|
9158
|
-
const converged = await client
|
|
9567
|
+
const converged = await postEvalEnsure(client, {
|
|
9159
9568
|
name: definition.name,
|
|
9160
|
-
definition
|
|
9569
|
+
definition,
|
|
9570
|
+
...passthrough
|
|
9161
9571
|
});
|
|
9162
9572
|
if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
|
|
9573
|
+
if (converged.result === "plan") throw new Error("Server returned a plan for a non-dryRun request");
|
|
9163
9574
|
memo.set(memoKey, converged.contentHash);
|
|
9164
9575
|
return converged;
|
|
9165
9576
|
}
|
|
@@ -9214,9 +9625,11 @@ function normalizeStepForHash(step) {
|
|
|
9214
9625
|
}
|
|
9215
9626
|
/** SHA-256 (hex) over the canonical normalized step list (steps only). */
|
|
9216
9627
|
async function computeFlowContentHash(steps) {
|
|
9217
|
-
const normalized =
|
|
9218
|
-
|
|
9219
|
-
|
|
9628
|
+
const normalized = steps.map((step) => isPlainObject$1(step) ? step : {}).map((step, index) => ({
|
|
9629
|
+
...step,
|
|
9630
|
+
name: typeof step.name === "string" ? step.name : `Step ${index + 1}`,
|
|
9631
|
+
order: typeof step.order === "number" ? step.order : index + 1
|
|
9632
|
+
})).sort((a, b) => a.order - b.order).map(normalizeStepForHash);
|
|
9220
9633
|
return sha256Hex(JSON.stringify(normalized));
|
|
9221
9634
|
}
|
|
9222
9635
|
const DEFINE_FLOW_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -9235,8 +9648,8 @@ const DEFINE_FLOW_STEP_KEYS = /* @__PURE__ */ new Set([
|
|
|
9235
9648
|
/**
|
|
9236
9649
|
* Collect account-scoped `tool_…` references from a step config (inlined copy
|
|
9237
9650
|
* of `collectNonPortableAgentToolRefs` in @runtypelabs/shared, applied to the
|
|
9238
|
-
* step's `config.tools` block and
|
|
9239
|
-
*
|
|
9651
|
+
* step's `config.tools` block, `toolId` and `agentId`). The v1 ensure surface
|
|
9652
|
+
* accepts portable tool references only.
|
|
9240
9653
|
*/
|
|
9241
9654
|
function collectStepNonPortableToolRefs(config, path) {
|
|
9242
9655
|
const found = [];
|
|
@@ -9270,12 +9683,73 @@ function collectStepNonPortableToolRefs(config, path) {
|
|
|
9270
9683
|
}
|
|
9271
9684
|
if (isAccountScoped(config.toolId)) found.push(`${path}.toolId`);
|
|
9272
9685
|
if (isRawId(config.agentId, "agent_")) found.push(`${path}.agentId`);
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9686
|
+
return found;
|
|
9687
|
+
}
|
|
9688
|
+
const CONDITIONAL_BRANCH_STEP_KEYS = [
|
|
9689
|
+
"trueSteps",
|
|
9690
|
+
"falseSteps",
|
|
9691
|
+
"true_steps",
|
|
9692
|
+
"false_steps",
|
|
9693
|
+
"otherwiseSteps"
|
|
9694
|
+
];
|
|
9695
|
+
/** Guards a hostile payload whose nesting is otherwise unbounded. */
|
|
9696
|
+
const MAX_REF_SITE_WALK_DEPTH = 100;
|
|
9697
|
+
/**
|
|
9698
|
+
* Collect account-scoped references at every flow step ref site (inlined copy of
|
|
9699
|
+
* `collectNonPortableFlowStepRefs` in @runtypelabs/shared): loop bodies, conditional
|
|
9700
|
+
* branches, inline flow-as-tool payloads and `errorHandling.fallbacks`.
|
|
9701
|
+
*/
|
|
9702
|
+
function collectFlowStepsNonPortableRefs(steps, basePath, depth = 0) {
|
|
9703
|
+
if (!Array.isArray(steps) || depth > MAX_REF_SITE_WALK_DEPTH) return [];
|
|
9704
|
+
const found = [];
|
|
9705
|
+
steps.forEach((step, index) => {
|
|
9706
|
+
if (!isPlainObject$1(step) || !isPlainObject$1(step.config)) return;
|
|
9707
|
+
found.push(...collectStepConfigSiteRefs(step.type, step.config, `${basePath}[${index}].config`, depth));
|
|
9708
|
+
});
|
|
9709
|
+
return found;
|
|
9710
|
+
}
|
|
9711
|
+
function collectStepConfigSiteRefs(type, config, path, depth) {
|
|
9712
|
+
const found = collectStepNonPortableToolRefs(config, path);
|
|
9713
|
+
found.push(...collectToolBagFlowRefs(config.tools, `${path}.tools`, depth + 1));
|
|
9714
|
+
const errorHandling = config.errorHandling;
|
|
9715
|
+
if (isPlainObject$1(errorHandling) && Array.isArray(errorHandling.fallbacks)) errorHandling.fallbacks.forEach((fallback, i) => {
|
|
9716
|
+
if (!isPlainObject$1(fallback)) return;
|
|
9717
|
+
const fallbackPath = `${path}.errorHandling.fallbacks[${i}]`;
|
|
9718
|
+
if (fallback.type === "flow") {
|
|
9719
|
+
if (typeof fallback.flowId === "string" && fallback.flowId.startsWith("flow_")) found.push(`${fallbackPath}.flowId`);
|
|
9720
|
+
} else if (fallback.type === "step" && isPlainObject$1(fallback.stepConfig)) {
|
|
9721
|
+
found.push(...collectStepNonPortableToolRefs(fallback.stepConfig, `${fallbackPath}.stepConfig`));
|
|
9722
|
+
found.push(...collectToolBagFlowRefs(fallback.stepConfig.tools, `${fallbackPath}.stepConfig.tools`, depth + 1));
|
|
9723
|
+
}
|
|
9724
|
+
});
|
|
9725
|
+
if (type === "loop") found.push(...collectFlowStepsNonPortableRefs(config.steps, `${path}.steps`, depth + 1));
|
|
9726
|
+
else if (type === "conditional") {
|
|
9727
|
+
if (Array.isArray(config.branches)) config.branches.forEach((branch, i) => {
|
|
9728
|
+
if (!isPlainObject$1(branch)) return;
|
|
9729
|
+
found.push(...collectFlowStepsNonPortableRefs(branch.steps, `${path}.branches[${i}].steps`, depth + 1));
|
|
9278
9730
|
});
|
|
9731
|
+
for (const key of CONDITIONAL_BRANCH_STEP_KEYS) found.push(...collectFlowStepsNonPortableRefs(config[key], `${path}.${key}`, depth + 1));
|
|
9732
|
+
}
|
|
9733
|
+
return found;
|
|
9734
|
+
}
|
|
9735
|
+
/** Descend the inline flow-as-tool and skill payloads a step's tool bag can carry. */
|
|
9736
|
+
function collectToolBagFlowRefs(tools, path, depth) {
|
|
9737
|
+
if (!isPlainObject$1(tools) || depth > MAX_REF_SITE_WALK_DEPTH) return [];
|
|
9738
|
+
const found = [];
|
|
9739
|
+
const walkBag = (bag, bagPath, bagDepth) => {
|
|
9740
|
+
if (bagDepth > MAX_REF_SITE_WALK_DEPTH) return;
|
|
9741
|
+
bag.forEach((tool, i) => {
|
|
9742
|
+
if (!isPlainObject$1(tool) || !isPlainObject$1(tool.config)) return;
|
|
9743
|
+
const toolPath = `${bagPath}[${i}].config`;
|
|
9744
|
+
const skill = tool.config.skill;
|
|
9745
|
+
if (isPlainObject$1(skill) && Array.isArray(skill.resolvedTools)) walkBag(skill.resolvedTools, `${toolPath}.skill.resolvedTools`, bagDepth + 1);
|
|
9746
|
+
const flow = tool.config.flow;
|
|
9747
|
+
if (isPlainObject$1(flow) && Array.isArray(flow.steps)) found.push(...collectFlowStepsNonPortableRefs(flow.steps, `${toolPath}.flow.steps`, bagDepth + 1));
|
|
9748
|
+
});
|
|
9749
|
+
};
|
|
9750
|
+
for (const key of ["runtimeTools", "inlineTools"]) {
|
|
9751
|
+
const bag = tools[key];
|
|
9752
|
+
if (Array.isArray(bag)) walkBag(bag, `${path}.${key}`, depth);
|
|
9279
9753
|
}
|
|
9280
9754
|
return found;
|
|
9281
9755
|
}
|
|
@@ -9316,7 +9790,7 @@ function defineFlow(input) {
|
|
|
9316
9790
|
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
9791
|
const config = isPlainObject$1(step.config) ? step.config : void 0;
|
|
9318
9792
|
if (config) {
|
|
9319
|
-
const nonPortable =
|
|
9793
|
+
const nonPortable = collectStepConfigSiteRefs(step.type, config, `steps[${index}].config`, 0);
|
|
9320
9794
|
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
9795
|
}
|
|
9322
9796
|
return {
|
|
@@ -9370,7 +9844,7 @@ var FlowEnsureConflictError = class extends Error {
|
|
|
9370
9844
|
/** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
|
|
9371
9845
|
var FlowDriftError = class extends Error {
|
|
9372
9846
|
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.`);
|
|
9847
|
+
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
9848
|
this.name = "FlowDriftError";
|
|
9375
9849
|
this.plan = plan;
|
|
9376
9850
|
}
|
|
@@ -9379,7 +9853,7 @@ var FlowDriftError = class extends Error {
|
|
|
9379
9853
|
* Best-effort extraction of `{ status, body }` from the generic request
|
|
9380
9854
|
* error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
|
|
9381
9855
|
*/
|
|
9382
|
-
function parseRequestError$
|
|
9856
|
+
function parseRequestError$6(err) {
|
|
9383
9857
|
if (!(err instanceof Error)) return {
|
|
9384
9858
|
status: null,
|
|
9385
9859
|
body: null
|
|
@@ -9401,8 +9875,8 @@ function parseRequestError$5(err) {
|
|
|
9401
9875
|
};
|
|
9402
9876
|
}
|
|
9403
9877
|
}
|
|
9404
|
-
function toConflictError$
|
|
9405
|
-
const { status, body } = parseRequestError$
|
|
9878
|
+
function toConflictError$6(err) {
|
|
9879
|
+
const { status, body } = parseRequestError$6(err);
|
|
9406
9880
|
if (status !== 409 || !isPlainObject$1(body)) return null;
|
|
9407
9881
|
const code = body.code;
|
|
9408
9882
|
if (code !== "external_modification" && code !== "remote_changed") return null;
|
|
@@ -9418,23 +9892,23 @@ function toConflictError$5(err) {
|
|
|
9418
9892
|
* so a stale server hash from the previous definition version can never be
|
|
9419
9893
|
* probed (which would false-match and silently skip the update).
|
|
9420
9894
|
*/
|
|
9421
|
-
const serverHashMemo$
|
|
9422
|
-
function memoFor$
|
|
9423
|
-
let memo = serverHashMemo$
|
|
9895
|
+
const serverHashMemo$6 = /* @__PURE__ */ new WeakMap();
|
|
9896
|
+
function memoFor$6(client) {
|
|
9897
|
+
let memo = serverHashMemo$6.get(client);
|
|
9424
9898
|
if (!memo) {
|
|
9425
9899
|
memo = /* @__PURE__ */ new Map();
|
|
9426
|
-
serverHashMemo$
|
|
9900
|
+
serverHashMemo$6.set(client, memo);
|
|
9427
9901
|
}
|
|
9428
9902
|
return memo;
|
|
9429
9903
|
}
|
|
9430
9904
|
function memoize$4(memo, memoKey, result) {
|
|
9431
9905
|
if (result.result !== "plan") memo.set(memoKey, result.contentHash);
|
|
9432
9906
|
}
|
|
9433
|
-
async function request$
|
|
9907
|
+
async function request$5(client, body) {
|
|
9434
9908
|
try {
|
|
9435
9909
|
return await client.post("/flows/ensure", body);
|
|
9436
9910
|
} catch (err) {
|
|
9437
|
-
const conflict = toConflictError$
|
|
9911
|
+
const conflict = toConflictError$6(err);
|
|
9438
9912
|
if (conflict) throw conflict;
|
|
9439
9913
|
throw err;
|
|
9440
9914
|
}
|
|
@@ -9459,37 +9933,37 @@ async function ensureFlow(client, definition, options = {}) {
|
|
|
9459
9933
|
steps: definition.steps
|
|
9460
9934
|
};
|
|
9461
9935
|
if (dryRun || expectNoChanges) {
|
|
9462
|
-
const plan = await request$
|
|
9936
|
+
const plan = await request$5(client, {
|
|
9463
9937
|
name: definition.name,
|
|
9464
9938
|
definition: wireDefinition,
|
|
9465
9939
|
dryRun: true,
|
|
9466
9940
|
...passthrough
|
|
9467
9941
|
});
|
|
9468
9942
|
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);
|
|
9943
|
+
if (expectNoChanges && (plan.changes !== "none" || release === "publish" && plan.publishPending === true)) throw new FlowDriftError(plan);
|
|
9470
9944
|
return plan;
|
|
9471
9945
|
}
|
|
9472
|
-
const memo = memoFor$
|
|
9946
|
+
const memo = memoFor$6(client);
|
|
9473
9947
|
const localHash = await computeFlowContentHash(definition.steps);
|
|
9474
9948
|
const memoKey = `${definition.name} ${localHash}`;
|
|
9475
9949
|
const contentHash = memo.get(memoKey) ?? localHash;
|
|
9476
|
-
const probe = await request$
|
|
9950
|
+
const probe = await request$5(client, {
|
|
9477
9951
|
name: definition.name,
|
|
9478
9952
|
contentHash,
|
|
9479
9953
|
...passthrough
|
|
9480
9954
|
});
|
|
9481
9955
|
if (probe.result !== "definitionRequired") {
|
|
9482
9956
|
memoize$4(memo, memoKey, probe);
|
|
9483
|
-
return convergeInlineEvals(client, definition, probe);
|
|
9957
|
+
return convergeInlineEvals(client, definition, probe, onConflict);
|
|
9484
9958
|
}
|
|
9485
|
-
const converged = await request$
|
|
9959
|
+
const converged = await request$5(client, {
|
|
9486
9960
|
name: definition.name,
|
|
9487
9961
|
definition: wireDefinition,
|
|
9488
9962
|
...passthrough
|
|
9489
9963
|
});
|
|
9490
9964
|
if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
|
|
9491
9965
|
memoize$4(memo, memoKey, converged);
|
|
9492
|
-
return convergeInlineEvals(client, definition, converged);
|
|
9966
|
+
return convergeInlineEvals(client, definition, converged, onConflict);
|
|
9493
9967
|
}
|
|
9494
9968
|
/**
|
|
9495
9969
|
* After the flow itself converges on the real (non-plan) path, converge each
|
|
@@ -9502,10 +9976,10 @@ async function ensureFlow(client, definition, options = {}) {
|
|
|
9502
9976
|
* unchanged (no empty `evals` array). The server's `/flows/ensure` surface is
|
|
9503
9977
|
* never touched by eval semantics — they ride the eval endpoints only.
|
|
9504
9978
|
*/
|
|
9505
|
-
async function convergeInlineEvals(client, definition, result) {
|
|
9979
|
+
async function convergeInlineEvals(client, definition, result, onConflict) {
|
|
9506
9980
|
if (result.result === "plan" || !definition.evals?.length) return result;
|
|
9507
9981
|
const evals = [];
|
|
9508
|
-
for (const evalDef of definition.evals) evals.push(await ensureEval(client, evalDef));
|
|
9982
|
+
for (const evalDef of definition.evals) evals.push(await ensureEval(client, evalDef, onConflict ? { onConflict } : {}));
|
|
9509
9983
|
return {
|
|
9510
9984
|
...result,
|
|
9511
9985
|
evals
|
|
@@ -10741,24 +11215,8 @@ var EvalsNamespace = class {
|
|
|
10741
11215
|
run(config) {
|
|
10742
11216
|
return new EvalRunner(this.getClient, config);
|
|
10743
11217
|
}
|
|
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);
|
|
11218
|
+
async ensure(definition, options = {}) {
|
|
11219
|
+
return ensureEval(this.getClient(), definition, options);
|
|
10762
11220
|
}
|
|
10763
11221
|
/**
|
|
10764
11222
|
* Pull the canonical definition + provenance for an eval suite by name — the
|
|
@@ -11025,7 +11483,7 @@ var SkillDriftError = class extends Error {
|
|
|
11025
11483
|
* Best-effort extraction of `{ status, body }` from the generic request error
|
|
11026
11484
|
* thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
|
|
11027
11485
|
*/
|
|
11028
|
-
function parseRequestError$
|
|
11486
|
+
function parseRequestError$5(err) {
|
|
11029
11487
|
if (!(err instanceof Error)) return {
|
|
11030
11488
|
status: null,
|
|
11031
11489
|
body: null
|
|
@@ -11047,8 +11505,8 @@ function parseRequestError$4(err) {
|
|
|
11047
11505
|
};
|
|
11048
11506
|
}
|
|
11049
11507
|
}
|
|
11050
|
-
function toConflictError$
|
|
11051
|
-
const { status, body } = parseRequestError$
|
|
11508
|
+
function toConflictError$5(err) {
|
|
11509
|
+
const { status, body } = parseRequestError$5(err);
|
|
11052
11510
|
if (status !== 409 || !isPlainObject$1(body)) return null;
|
|
11053
11511
|
const code = body.code;
|
|
11054
11512
|
if (code !== "external_modification" && code !== "remote_changed") return null;
|
|
@@ -11060,23 +11518,23 @@ function toConflictError$4(err) {
|
|
|
11060
11518
|
* within the process lifetime invalidates a stale server hash (which would
|
|
11061
11519
|
* false-match and silently skip the update). Same scheme as flows/tools.ensure.
|
|
11062
11520
|
*/
|
|
11063
|
-
const serverHashMemo$
|
|
11064
|
-
function memoFor$
|
|
11065
|
-
let memo = serverHashMemo$
|
|
11521
|
+
const serverHashMemo$5 = /* @__PURE__ */ new WeakMap();
|
|
11522
|
+
function memoFor$5(client) {
|
|
11523
|
+
let memo = serverHashMemo$5.get(client);
|
|
11066
11524
|
if (!memo) {
|
|
11067
11525
|
memo = /* @__PURE__ */ new Map();
|
|
11068
|
-
serverHashMemo$
|
|
11526
|
+
serverHashMemo$5.set(client, memo);
|
|
11069
11527
|
}
|
|
11070
11528
|
return memo;
|
|
11071
11529
|
}
|
|
11072
11530
|
function memoize$3(memo, memoKey, result) {
|
|
11073
11531
|
if (result.result !== "plan") memo.set(memoKey, result.contentHash);
|
|
11074
11532
|
}
|
|
11075
|
-
async function request$
|
|
11533
|
+
async function request$4(client, body) {
|
|
11076
11534
|
try {
|
|
11077
11535
|
return await client.post("/skills/ensure", body);
|
|
11078
11536
|
} catch (err) {
|
|
11079
|
-
const conflict = toConflictError$
|
|
11537
|
+
const conflict = toConflictError$5(err);
|
|
11080
11538
|
if (conflict) throw conflict;
|
|
11081
11539
|
throw err;
|
|
11082
11540
|
}
|
|
@@ -11088,14 +11546,15 @@ async function request$3(client, body) {
|
|
|
11088
11546
|
* on every change; never deletes.
|
|
11089
11547
|
*/
|
|
11090
11548
|
async function ensureSkill(client, definition, options = {}) {
|
|
11091
|
-
const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
|
|
11549
|
+
const { dryRun, onConflict, release, expectedRemoteHash, version, expectNoChanges } = options;
|
|
11092
11550
|
const passthrough = {
|
|
11093
11551
|
...onConflict ? { onConflict } : {},
|
|
11094
11552
|
...release ? { release } : {},
|
|
11095
|
-
...expectedRemoteHash ? { expectedRemoteHash } : {}
|
|
11553
|
+
...expectedRemoteHash ? { expectedRemoteHash } : {},
|
|
11554
|
+
...version ? { version } : {}
|
|
11096
11555
|
};
|
|
11097
11556
|
if (dryRun || expectNoChanges) {
|
|
11098
|
-
const plan = await request$
|
|
11557
|
+
const plan = await request$4(client, {
|
|
11099
11558
|
name: definition.name,
|
|
11100
11559
|
definition: manifestToWire(definition),
|
|
11101
11560
|
dryRun: true,
|
|
@@ -11105,11 +11564,11 @@ async function ensureSkill(client, definition, options = {}) {
|
|
|
11105
11564
|
if (expectNoChanges && plan.changes !== "none") throw new SkillDriftError(plan);
|
|
11106
11565
|
return plan;
|
|
11107
11566
|
}
|
|
11108
|
-
const memo = memoFor$
|
|
11567
|
+
const memo = memoFor$5(client);
|
|
11109
11568
|
const localHash = await computeSkillContentHash(definition);
|
|
11110
11569
|
const memoKey = `${definition.name} ${localHash}`;
|
|
11111
11570
|
const contentHash = memo.get(memoKey) ?? localHash;
|
|
11112
|
-
const probe = await request$
|
|
11571
|
+
const probe = await request$4(client, {
|
|
11113
11572
|
name: definition.name,
|
|
11114
11573
|
contentHash,
|
|
11115
11574
|
...passthrough
|
|
@@ -11118,7 +11577,7 @@ async function ensureSkill(client, definition, options = {}) {
|
|
|
11118
11577
|
memoize$3(memo, memoKey, probe);
|
|
11119
11578
|
return probe;
|
|
11120
11579
|
}
|
|
11121
|
-
const converged = await request$
|
|
11580
|
+
const converged = await request$4(client, {
|
|
11122
11581
|
name: definition.name,
|
|
11123
11582
|
definition: manifestToWire(definition),
|
|
11124
11583
|
...passthrough
|
|
@@ -11534,6 +11993,18 @@ function defineAgent(input) {
|
|
|
11534
11993
|
config
|
|
11535
11994
|
};
|
|
11536
11995
|
}
|
|
11996
|
+
function planHasSkillBindingChanges(plan) {
|
|
11997
|
+
return (plan.skillBindings ?? []).some((binding) => binding.action !== "unchanged");
|
|
11998
|
+
}
|
|
11999
|
+
/** Whether a dry run that asked for a deploy would move the alias or change its binding names. */
|
|
12000
|
+
function planHasDeploymentChanges(plan) {
|
|
12001
|
+
return plan.deployment?.changed === true || plan.deployment?.bindingsChanged === true;
|
|
12002
|
+
}
|
|
12003
|
+
function deploymentChangeLabels(plan) {
|
|
12004
|
+
const deployment = plan.deployment;
|
|
12005
|
+
if (!deployment) return [];
|
|
12006
|
+
return [...deployment.changed ? [`deploy:${deployment.alias} (pointer)`] : [], ...deployment.bindingsChanged ? [`deploy:${deployment.alias} (bindings)`] : []];
|
|
12007
|
+
}
|
|
11537
12008
|
/** A 409 from the ensure protocol (external_modification or remote_changed). */
|
|
11538
12009
|
var AgentEnsureConflictError = class extends Error {
|
|
11539
12010
|
constructor(body) {
|
|
@@ -11548,7 +12019,12 @@ var AgentEnsureConflictError = class extends Error {
|
|
|
11548
12019
|
/** Thrown by `ensure(def, { expectNoChanges: true })` when drift is detected. */
|
|
11549
12020
|
var AgentDriftError = class extends Error {
|
|
11550
12021
|
constructor(plan) {
|
|
11551
|
-
|
|
12022
|
+
const skillChanges = (plan.skillBindings ?? []).filter((binding) => binding.action !== "unchanged").map((binding) => `skill:${binding.name} (${binding.action})`);
|
|
12023
|
+
super(`Agent "${plan.agentId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${[
|
|
12024
|
+
...plan.changedKeys,
|
|
12025
|
+
...skillChanges,
|
|
12026
|
+
...deploymentChangeLabels(plan)
|
|
12027
|
+
].join(", ") || "n/a"}). Run client.agents.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`);
|
|
11552
12028
|
this.name = "AgentDriftError";
|
|
11553
12029
|
this.plan = plan;
|
|
11554
12030
|
}
|
|
@@ -11557,7 +12033,7 @@ var AgentDriftError = class extends Error {
|
|
|
11557
12033
|
* Best-effort extraction of `{ status, body }` from the generic request
|
|
11558
12034
|
* error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
|
|
11559
12035
|
*/
|
|
11560
|
-
function parseRequestError$
|
|
12036
|
+
function parseRequestError$4(err) {
|
|
11561
12037
|
if (!(err instanceof Error)) return {
|
|
11562
12038
|
status: null,
|
|
11563
12039
|
body: null
|
|
@@ -11579,8 +12055,8 @@ function parseRequestError$3(err) {
|
|
|
11579
12055
|
};
|
|
11580
12056
|
}
|
|
11581
12057
|
}
|
|
11582
|
-
function toConflictError$
|
|
11583
|
-
const { status, body } = parseRequestError$
|
|
12058
|
+
function toConflictError$4(err) {
|
|
12059
|
+
const { status, body } = parseRequestError$4(err);
|
|
11584
12060
|
if (status !== 409 || !isPlainObject(body)) return null;
|
|
11585
12061
|
const code = body.code;
|
|
11586
12062
|
if (code !== "external_modification" && code !== "remote_changed") return null;
|
|
@@ -11596,12 +12072,12 @@ function toConflictError$3(err) {
|
|
|
11596
12072
|
* so a stale server hash from the previous definition version can never be
|
|
11597
12073
|
* probed (which would false-match and silently skip the update).
|
|
11598
12074
|
*/
|
|
11599
|
-
const serverHashMemo$
|
|
11600
|
-
function memoFor$
|
|
11601
|
-
let memo = serverHashMemo$
|
|
12075
|
+
const serverHashMemo$4 = /* @__PURE__ */ new WeakMap();
|
|
12076
|
+
function memoFor$4(client) {
|
|
12077
|
+
let memo = serverHashMemo$4.get(client);
|
|
11602
12078
|
if (!memo) {
|
|
11603
12079
|
memo = /* @__PURE__ */ new Map();
|
|
11604
|
-
serverHashMemo$
|
|
12080
|
+
serverHashMemo$4.set(client, memo);
|
|
11605
12081
|
}
|
|
11606
12082
|
return memo;
|
|
11607
12083
|
}
|
|
@@ -11637,14 +12113,16 @@ var AgentsNamespace = class {
|
|
|
11637
12113
|
*/
|
|
11638
12114
|
async ensure(definition, options = {}) {
|
|
11639
12115
|
const client = this.getClient();
|
|
11640
|
-
const { dryRun, onConflict, release, deploy, expectedRemoteHash, version, expectNoChanges } = options;
|
|
12116
|
+
const { dryRun, onConflict, release, deploy, expectedRemoteHash, version, expectNoChanges, skills, capabilities } = options;
|
|
11641
12117
|
if (release !== void 0 && deploy !== void 0) throw new Error(ENSURE_RELEASE_DEPLOY_CONFLICT_MESSAGE);
|
|
11642
12118
|
const passthrough = {
|
|
11643
12119
|
...onConflict ? { onConflict } : {},
|
|
11644
12120
|
...release ? { release } : {},
|
|
11645
12121
|
...deploy ? { deploy } : {},
|
|
11646
12122
|
...expectedRemoteHash ? { expectedRemoteHash } : {},
|
|
11647
|
-
...version ? { version } : {}
|
|
12123
|
+
...version ? { version } : {},
|
|
12124
|
+
...skills ? { skills } : {},
|
|
12125
|
+
...capabilities ? { capabilities } : {}
|
|
11648
12126
|
};
|
|
11649
12127
|
if (dryRun || expectNoChanges) {
|
|
11650
12128
|
const plan = await this.request(client, {
|
|
@@ -11654,15 +12132,16 @@ var AgentsNamespace = class {
|
|
|
11654
12132
|
...passthrough
|
|
11655
12133
|
});
|
|
11656
12134
|
if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
|
|
11657
|
-
|
|
12135
|
+
const deployRequested = release === "publish" || deploy !== void 0;
|
|
12136
|
+
if (expectNoChanges && (plan.changes !== "none" || planHasSkillBindingChanges(plan) || deployRequested && planHasDeploymentChanges(plan))) throw new AgentDriftError(plan);
|
|
11658
12137
|
return plan;
|
|
11659
12138
|
}
|
|
11660
|
-
const memo = memoFor$
|
|
12139
|
+
const memo = memoFor$4(client);
|
|
11661
12140
|
const localHash = await computeAgentContentHash({
|
|
11662
12141
|
...definition,
|
|
11663
12142
|
config: definition.config
|
|
11664
12143
|
});
|
|
11665
|
-
const memoKey = `${definition.name}\u0000${localHash}`;
|
|
12144
|
+
const memoKey = `${definition.name}\u0000${localHash}${capabilities ? `\u0000${JSON.stringify(capabilities)}` : ""}`;
|
|
11666
12145
|
const contentHash = memo.get(memoKey) ?? localHash;
|
|
11667
12146
|
const probe = await this.request(client, {
|
|
11668
12147
|
name: definition.name,
|
|
@@ -11696,7 +12175,7 @@ var AgentsNamespace = class {
|
|
|
11696
12175
|
try {
|
|
11697
12176
|
return await client.post("/agents/ensure", body);
|
|
11698
12177
|
} catch (err) {
|
|
11699
|
-
const conflict = toConflictError$
|
|
12178
|
+
const conflict = toConflictError$4(err);
|
|
11700
12179
|
if (conflict) throw conflict;
|
|
11701
12180
|
throw err;
|
|
11702
12181
|
}
|
|
@@ -11825,7 +12304,7 @@ var ToolDriftError = class extends Error {
|
|
|
11825
12304
|
* Best-effort extraction of `{ status, body }` from the generic request
|
|
11826
12305
|
* error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
|
|
11827
12306
|
*/
|
|
11828
|
-
function parseRequestError$
|
|
12307
|
+
function parseRequestError$3(err) {
|
|
11829
12308
|
if (!(err instanceof Error)) return {
|
|
11830
12309
|
status: null,
|
|
11831
12310
|
body: null
|
|
@@ -11847,8 +12326,8 @@ function parseRequestError$2(err) {
|
|
|
11847
12326
|
};
|
|
11848
12327
|
}
|
|
11849
12328
|
}
|
|
11850
|
-
function toConflictError$
|
|
11851
|
-
const { status, body } = parseRequestError$
|
|
12329
|
+
function toConflictError$3(err) {
|
|
12330
|
+
const { status, body } = parseRequestError$3(err);
|
|
11852
12331
|
if (status !== 409 || !isPlainObject$1(body)) return null;
|
|
11853
12332
|
const code = body.code;
|
|
11854
12333
|
if (code !== "external_modification" && code !== "remote_changed") return null;
|
|
@@ -11860,23 +12339,23 @@ function toConflictError$2(err) {
|
|
|
11860
12339
|
* within the process lifetime invalidates a stale server hash (which would
|
|
11861
12340
|
* false-match and silently skip the update). Same scheme as flows.ensure.
|
|
11862
12341
|
*/
|
|
11863
|
-
const serverHashMemo$
|
|
11864
|
-
function memoFor$
|
|
11865
|
-
let memo = serverHashMemo$
|
|
12342
|
+
const serverHashMemo$3 = /* @__PURE__ */ new WeakMap();
|
|
12343
|
+
function memoFor$3(client) {
|
|
12344
|
+
let memo = serverHashMemo$3.get(client);
|
|
11866
12345
|
if (!memo) {
|
|
11867
12346
|
memo = /* @__PURE__ */ new Map();
|
|
11868
|
-
serverHashMemo$
|
|
12347
|
+
serverHashMemo$3.set(client, memo);
|
|
11869
12348
|
}
|
|
11870
12349
|
return memo;
|
|
11871
12350
|
}
|
|
11872
12351
|
function memoize$2(memo, memoKey, result) {
|
|
11873
12352
|
if (result.result !== "plan") memo.set(memoKey, result.contentHash);
|
|
11874
12353
|
}
|
|
11875
|
-
async function request$
|
|
12354
|
+
async function request$3(client, body) {
|
|
11876
12355
|
try {
|
|
11877
12356
|
return await client.post("/tools/ensure", body);
|
|
11878
12357
|
} catch (err) {
|
|
11879
|
-
const conflict = toConflictError$
|
|
12358
|
+
const conflict = toConflictError$3(err);
|
|
11880
12359
|
if (conflict) throw conflict;
|
|
11881
12360
|
throw err;
|
|
11882
12361
|
}
|
|
@@ -11893,7 +12372,7 @@ async function ensureTool(client, definition, options = {}) {
|
|
|
11893
12372
|
...expectedRemoteHash ? { expectedRemoteHash } : {}
|
|
11894
12373
|
};
|
|
11895
12374
|
if (dryRun || expectNoChanges) {
|
|
11896
|
-
const plan = await request$
|
|
12375
|
+
const plan = await request$3(client, {
|
|
11897
12376
|
name: definition.name,
|
|
11898
12377
|
definition,
|
|
11899
12378
|
dryRun: true,
|
|
@@ -11903,11 +12382,11 @@ async function ensureTool(client, definition, options = {}) {
|
|
|
11903
12382
|
if (expectNoChanges && plan.changes !== "none") throw new ToolDriftError(plan);
|
|
11904
12383
|
return plan;
|
|
11905
12384
|
}
|
|
11906
|
-
const memo = memoFor$
|
|
12385
|
+
const memo = memoFor$3(client);
|
|
11907
12386
|
const localHash = await computeToolContentHash(definition);
|
|
11908
12387
|
const memoKey = `${definition.name} ${localHash}`;
|
|
11909
12388
|
const contentHash = memo.get(memoKey) ?? localHash;
|
|
11910
|
-
const probe = await request$
|
|
12389
|
+
const probe = await request$3(client, {
|
|
11911
12390
|
name: definition.name,
|
|
11912
12391
|
contentHash,
|
|
11913
12392
|
...passthrough
|
|
@@ -11916,7 +12395,7 @@ async function ensureTool(client, definition, options = {}) {
|
|
|
11916
12395
|
memoize$2(memo, memoKey, probe);
|
|
11917
12396
|
return probe;
|
|
11918
12397
|
}
|
|
11919
|
-
const converged = await request$
|
|
12398
|
+
const converged = await request$3(client, {
|
|
11920
12399
|
name: definition.name,
|
|
11921
12400
|
definition,
|
|
11922
12401
|
...passthrough
|
|
@@ -11972,10 +12451,197 @@ var ToolsNamespace = class {
|
|
|
11972
12451
|
}
|
|
11973
12452
|
};
|
|
11974
12453
|
//#endregion
|
|
12454
|
+
//#region src/question-sets-ensure.ts
|
|
12455
|
+
/** Canonical form of a question set definition, without its slug. */
|
|
12456
|
+
function normalizeQuestionSetDefinition(definition) {
|
|
12457
|
+
return normalizeValue$1({
|
|
12458
|
+
name: definition.name,
|
|
12459
|
+
description: definition.description,
|
|
12460
|
+
questions: isPlainObject$1(definition.questions) ? definition.questions : {},
|
|
12461
|
+
inputMapping: definition.inputMapping,
|
|
12462
|
+
model: definition.model
|
|
12463
|
+
});
|
|
12464
|
+
}
|
|
12465
|
+
/** SHA-256 (hex) over the canonical question set definition. */
|
|
12466
|
+
async function computeQuestionSetContentHash(definition) {
|
|
12467
|
+
return sha256Hex(JSON.stringify(normalizeQuestionSetDefinition(definition)));
|
|
12468
|
+
}
|
|
12469
|
+
const SLUG_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
12470
|
+
const DEFINE_QUESTION_SET_KEYS = /* @__PURE__ */ new Set([
|
|
12471
|
+
"slug",
|
|
12472
|
+
"name",
|
|
12473
|
+
"description",
|
|
12474
|
+
"questions",
|
|
12475
|
+
"inputMapping",
|
|
12476
|
+
"model"
|
|
12477
|
+
]);
|
|
12478
|
+
/**
|
|
12479
|
+
* Pure local constructor for a question set definition. Checks the structure
|
|
12480
|
+
* and rejects unknown fields; the server validates the questions at ensure time.
|
|
12481
|
+
*
|
|
12482
|
+
* @example
|
|
12483
|
+
* ```typescript
|
|
12484
|
+
* const triage = defineQuestionSet({
|
|
12485
|
+
* slug: 'ticket-triage',
|
|
12486
|
+
* name: 'Ticket triage',
|
|
12487
|
+
* questions: { urgent: { type: 'boolean', instructions: 'Is the ticket urgent?' } },
|
|
12488
|
+
* })
|
|
12489
|
+
* ```
|
|
12490
|
+
*/
|
|
12491
|
+
function defineQuestionSet(input) {
|
|
12492
|
+
if (!input || typeof input !== "object") throw new Error("defineQuestionSet requires a definition object");
|
|
12493
|
+
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");
|
|
12494
|
+
if (typeof input.name !== "string" || input.name.length === 0) throw new Error("defineQuestionSet requires a non-empty string \"name\"");
|
|
12495
|
+
if (!isPlainObject$1(input.questions) || Object.keys(input.questions).length === 0) throw new Error("defineQuestionSet requires a non-empty \"questions\" object");
|
|
12496
|
+
const unknownKeys = Object.keys(input).filter((key) => !DEFINE_QUESTION_SET_KEYS.has(key));
|
|
12497
|
+
if (unknownKeys.length > 0) throw new Error(`defineQuestionSet: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are slug, name, description, questions, inputMapping and model.`);
|
|
12498
|
+
return { ...input };
|
|
12499
|
+
}
|
|
12500
|
+
/** A 409 from the ensure protocol (external_modification or remote_changed). */
|
|
12501
|
+
var QuestionSetEnsureConflictError = class extends Error {
|
|
12502
|
+
constructor(body) {
|
|
12503
|
+
super(body.error ?? `Question set ensure conflict: ${body.code}`);
|
|
12504
|
+
this.name = "QuestionSetEnsureConflictError";
|
|
12505
|
+
this.code = body.code;
|
|
12506
|
+
this.lastModifiedSource = body.lastModifiedSource;
|
|
12507
|
+
this.modifiedAt = body.modifiedAt;
|
|
12508
|
+
this.currentHash = body.currentHash;
|
|
12509
|
+
}
|
|
12510
|
+
};
|
|
12511
|
+
/** Thrown by `ensure(def, { expectNoChanges: true })` when the remote differs. */
|
|
12512
|
+
var QuestionSetDriftError = class extends Error {
|
|
12513
|
+
constructor(slug, plan) {
|
|
12514
|
+
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.`);
|
|
12515
|
+
this.name = "QuestionSetDriftError";
|
|
12516
|
+
this.plan = plan;
|
|
12517
|
+
}
|
|
12518
|
+
};
|
|
12519
|
+
function toConflictError$2(err) {
|
|
12520
|
+
if (!(err instanceof Error)) return null;
|
|
12521
|
+
const match = err.message.match(/^API request failed: (409) .*? - ([\s\S]*)$/);
|
|
12522
|
+
if (!match) return null;
|
|
12523
|
+
let body;
|
|
12524
|
+
try {
|
|
12525
|
+
body = JSON.parse(match[2]);
|
|
12526
|
+
} catch {
|
|
12527
|
+
return null;
|
|
12528
|
+
}
|
|
12529
|
+
if (!isPlainObject$1(body)) return null;
|
|
12530
|
+
if (body.code !== "external_modification" && body.code !== "remote_changed") return null;
|
|
12531
|
+
return new QuestionSetEnsureConflictError(body);
|
|
12532
|
+
}
|
|
12533
|
+
const serverHashMemo$2 = /* @__PURE__ */ new WeakMap();
|
|
12534
|
+
function memoFor$2(client) {
|
|
12535
|
+
let memo = serverHashMemo$2.get(client);
|
|
12536
|
+
if (!memo) {
|
|
12537
|
+
memo = /* @__PURE__ */ new Map();
|
|
12538
|
+
serverHashMemo$2.set(client, memo);
|
|
12539
|
+
}
|
|
12540
|
+
return memo;
|
|
12541
|
+
}
|
|
12542
|
+
async function request$2(client, body) {
|
|
12543
|
+
try {
|
|
12544
|
+
return await client.post("/question-sets/ensure", body);
|
|
12545
|
+
} catch (err) {
|
|
12546
|
+
throw toConflictError$2(err) ?? err;
|
|
12547
|
+
}
|
|
12548
|
+
}
|
|
12549
|
+
function toWireDefinition(definition) {
|
|
12550
|
+
const { slug: _slug, ...rest } = definition;
|
|
12551
|
+
return rest;
|
|
12552
|
+
}
|
|
12553
|
+
/**
|
|
12554
|
+
* Idempotently converge a question set onto the platform. Probes with a
|
|
12555
|
+
* content hash first and sends the full definition only on a miss. Appends a
|
|
12556
|
+
* version when the questions, input mapping or model change; never deletes.
|
|
12557
|
+
*/
|
|
12558
|
+
async function ensureQuestionSet(client, definition, options = {}) {
|
|
12559
|
+
const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
|
|
12560
|
+
const passthrough = {
|
|
12561
|
+
...onConflict ? { onConflict } : {},
|
|
12562
|
+
...release ? { release } : {},
|
|
12563
|
+
...expectedRemoteHash ? { expectedRemoteHash } : {}
|
|
12564
|
+
};
|
|
12565
|
+
if (dryRun || expectNoChanges) {
|
|
12566
|
+
const plan = await request$2(client, {
|
|
12567
|
+
slug: definition.slug,
|
|
12568
|
+
definition: toWireDefinition(definition),
|
|
12569
|
+
dryRun: true,
|
|
12570
|
+
...passthrough
|
|
12571
|
+
});
|
|
12572
|
+
if (plan.result !== "plan") throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
|
|
12573
|
+
if (expectNoChanges && plan.changes !== "none") throw new QuestionSetDriftError(definition.slug, plan);
|
|
12574
|
+
return plan;
|
|
12575
|
+
}
|
|
12576
|
+
const memo = memoFor$2(client);
|
|
12577
|
+
const localHash = await computeQuestionSetContentHash(definition);
|
|
12578
|
+
const memoKey = `${definition.slug} ${localHash}`;
|
|
12579
|
+
const probe = await request$2(client, {
|
|
12580
|
+
slug: definition.slug,
|
|
12581
|
+
contentHash: memo.get(memoKey) ?? localHash,
|
|
12582
|
+
...passthrough
|
|
12583
|
+
});
|
|
12584
|
+
if (probe.result !== "definitionRequired") {
|
|
12585
|
+
if (probe.result !== "plan") memo.set(memoKey, probe.contentHash);
|
|
12586
|
+
return probe;
|
|
12587
|
+
}
|
|
12588
|
+
const converged = await request$2(client, {
|
|
12589
|
+
slug: definition.slug,
|
|
12590
|
+
definition: toWireDefinition(definition),
|
|
12591
|
+
...passthrough
|
|
12592
|
+
});
|
|
12593
|
+
if (converged.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-definition request");
|
|
12594
|
+
if (converged.result !== "plan") memo.set(memoKey, converged.contentHash);
|
|
12595
|
+
return converged;
|
|
12596
|
+
}
|
|
12597
|
+
/** Pull the canonical definition and provenance for a question set by slug. */
|
|
12598
|
+
async function pullQuestionSet(client, slug) {
|
|
12599
|
+
return client.get("/question-sets/pull", { slug });
|
|
12600
|
+
}
|
|
12601
|
+
//#endregion
|
|
12602
|
+
//#region src/question-sets-namespace.ts
|
|
12603
|
+
/** Question set config-as-code: `ensure` and `pull`. */
|
|
12604
|
+
var QuestionSetsNamespace = class {
|
|
12605
|
+
constructor(getClient) {
|
|
12606
|
+
this.getClient = getClient;
|
|
12607
|
+
}
|
|
12608
|
+
/**
|
|
12609
|
+
* Converge a `defineQuestionSet` definition. The steady state is one hash
|
|
12610
|
+
* probe. Pass `release: 'publish'` so flows and agents can resolve the
|
|
12611
|
+
* latest version.
|
|
12612
|
+
*
|
|
12613
|
+
* @example
|
|
12614
|
+
* ```typescript
|
|
12615
|
+
* await Runtype.questionSets.ensure(triage, { release: 'publish' })
|
|
12616
|
+
* await Runtype.questionSets.ensure(triage, { expectNoChanges: true })
|
|
12617
|
+
* ```
|
|
12618
|
+
*/
|
|
12619
|
+
async ensure(definition, options = {}) {
|
|
12620
|
+
return ensureQuestionSet(this.getClient(), definition, options);
|
|
12621
|
+
}
|
|
12622
|
+
/** Pull the live definition and provenance for a slug, to absorb a dashboard edit. */
|
|
12623
|
+
async pull(slug) {
|
|
12624
|
+
return pullQuestionSet(this.getClient(), slug);
|
|
12625
|
+
}
|
|
12626
|
+
};
|
|
12627
|
+
//#endregion
|
|
11975
12628
|
//#region src/products-ensure.ts
|
|
12629
|
+
/**
|
|
12630
|
+
* `spec` with its stack as a save stores it: without the server-set fields (`detected`, `declared`,
|
|
12631
|
+
* `hiddenAt`), which their own write paths set, and with `hidden` deduplicated.
|
|
12632
|
+
*/
|
|
12633
|
+
function asSavedStack(spec) {
|
|
12634
|
+
if (!isPlainObject$1(spec.stack)) return spec;
|
|
12635
|
+
const { detected: _detected, declared: _declared, hiddenAt: _hiddenAt, ...stack } = spec.stack;
|
|
12636
|
+
if (Array.isArray(stack.hidden)) stack.hidden = [...new Set(stack.hidden)];
|
|
12637
|
+
return {
|
|
12638
|
+
...spec,
|
|
12639
|
+
stack
|
|
12640
|
+
};
|
|
12641
|
+
}
|
|
11976
12642
|
/** Canonical normalized form of a product definition (name + canvas excluded). */
|
|
11977
12643
|
function normalizeProductDefinition(definition) {
|
|
11978
|
-
const spec = isPlainObject$1(definition.spec) ? normalizeValue$1(definition.spec) : {};
|
|
12644
|
+
const spec = isPlainObject$1(definition.spec) ? normalizeValue$1(asSavedStack(definition.spec)) : {};
|
|
11979
12645
|
return {
|
|
11980
12646
|
...definition.description ? { description: definition.description } : {},
|
|
11981
12647
|
...definition.icon ? { icon: definition.icon } : {},
|
|
@@ -12045,7 +12711,7 @@ var ProductDriftError = class extends Error {
|
|
|
12045
12711
|
* Best-effort extraction of `{ status, body }` from the generic request
|
|
12046
12712
|
* error thrown by RuntypeClient ("API request failed: 409 Conflict - {…}").
|
|
12047
12713
|
*/
|
|
12048
|
-
function parseRequestError$
|
|
12714
|
+
function parseRequestError$2(err) {
|
|
12049
12715
|
if (!(err instanceof Error)) return {
|
|
12050
12716
|
status: null,
|
|
12051
12717
|
body: null
|
|
@@ -12068,7 +12734,7 @@ function parseRequestError$1(err) {
|
|
|
12068
12734
|
}
|
|
12069
12735
|
}
|
|
12070
12736
|
function toConflictError$1(err) {
|
|
12071
|
-
const { status, body } = parseRequestError$
|
|
12737
|
+
const { status, body } = parseRequestError$2(err);
|
|
12072
12738
|
if (status !== 409 || !isPlainObject$1(body)) return null;
|
|
12073
12739
|
const code = body.code;
|
|
12074
12740
|
if (code !== "external_modification" && code !== "remote_changed") return null;
|
|
@@ -12190,20 +12856,85 @@ function defineFpo(fpo) {
|
|
|
12190
12856
|
if (!product || typeof product.name !== "string" || product.name.length === 0) throw new Error("defineFpo requires a non-empty \"product.name\" (the converge identity)");
|
|
12191
12857
|
return fpo;
|
|
12192
12858
|
}
|
|
12859
|
+
const FPO_DRIFT_RESULTS = /* @__PURE__ */ new Set([
|
|
12860
|
+
"created",
|
|
12861
|
+
"updated",
|
|
12862
|
+
"pruned",
|
|
12863
|
+
"failed"
|
|
12864
|
+
]);
|
|
12865
|
+
/** Thrown by `ensureFpo(fpo, { expectNoChanges: true })` when the plan is not clean. */
|
|
12866
|
+
var FpoDriftError = class extends Error {
|
|
12867
|
+
constructor(plan) {
|
|
12868
|
+
const drifted = plan.entities.filter((entity) => FPO_DRIFT_RESULTS.has(entity.result)).map((entity) => `${entity.kind}:${entity.name} (${entity.result})`);
|
|
12869
|
+
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.`);
|
|
12870
|
+
this.name = "FpoDriftError";
|
|
12871
|
+
this.plan = plan;
|
|
12872
|
+
}
|
|
12873
|
+
};
|
|
12874
|
+
function fpoPlanHasDrift(plan) {
|
|
12875
|
+
return plan.entities.some((entity) => FPO_DRIFT_RESULTS.has(entity.result));
|
|
12876
|
+
}
|
|
12877
|
+
function parseRequestError$1(err) {
|
|
12878
|
+
if (!(err instanceof Error)) return {
|
|
12879
|
+
status: null,
|
|
12880
|
+
body: null
|
|
12881
|
+
};
|
|
12882
|
+
const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
|
|
12883
|
+
if (!match) return {
|
|
12884
|
+
status: null,
|
|
12885
|
+
body: null
|
|
12886
|
+
};
|
|
12887
|
+
try {
|
|
12888
|
+
return {
|
|
12889
|
+
status: Number(match[1]),
|
|
12890
|
+
body: JSON.parse(match[2])
|
|
12891
|
+
};
|
|
12892
|
+
} catch {
|
|
12893
|
+
return {
|
|
12894
|
+
status: Number(match[1]),
|
|
12895
|
+
body: null
|
|
12896
|
+
};
|
|
12897
|
+
}
|
|
12898
|
+
}
|
|
12899
|
+
async function requestFpoEnsure(client, body) {
|
|
12900
|
+
try {
|
|
12901
|
+
return await client.post("/products/ensure-fpo", body);
|
|
12902
|
+
} catch (err) {
|
|
12903
|
+
const { status, body: errorBody } = parseRequestError$1(err);
|
|
12904
|
+
if (status === 409 && isPlainObject$1(errorBody) && (errorBody.code === "external_modification" || errorBody.code === "remote_changed")) throw new ProductEnsureConflictError(errorBody);
|
|
12905
|
+
throw err;
|
|
12906
|
+
}
|
|
12907
|
+
}
|
|
12193
12908
|
/**
|
|
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.
|
|
12909
|
+
* Converge an entire FPO onto the platform. Ships the full FPO unless a
|
|
12910
|
+
* drafts-only `hashProbe` finds the live graph already matches; the server fans out to the
|
|
12911
|
+
* per-entity ensure services and returns the whole-FPO hash + per-entity report.
|
|
12912
|
+
* Non-atomic: inspect `result.hasFailures` / `entities[].result` and re-run to self-heal.
|
|
12198
12913
|
*/
|
|
12199
12914
|
async function ensureFpo(client, fpo, options = {}) {
|
|
12200
|
-
const { dryRun, onConflict, prune } = options;
|
|
12201
|
-
|
|
12915
|
+
const { dryRun, onConflict, prune, expectNoChanges, expectedRemoteHash, hashProbe, release } = options;
|
|
12916
|
+
const plan = dryRun || expectNoChanges;
|
|
12917
|
+
const guard = expectedRemoteHash ? { expectedRemoteHash } : {};
|
|
12918
|
+
if (hashProbe && release === "none" && !plan && !prune) {
|
|
12919
|
+
const probe = await requestFpoEnsure(client, {
|
|
12920
|
+
name: (isPlainObject$1(fpo.product) ? fpo.product : {}).name,
|
|
12921
|
+
contentHash: await computeFpoContentHash(fpo),
|
|
12922
|
+
...guard,
|
|
12923
|
+
release
|
|
12924
|
+
});
|
|
12925
|
+
if (probe.result !== "definitionRequired") return probe;
|
|
12926
|
+
}
|
|
12927
|
+
const result = await requestFpoEnsure(client, {
|
|
12202
12928
|
fpo,
|
|
12203
|
-
...
|
|
12929
|
+
...plan ? { dryRun: true } : {},
|
|
12204
12930
|
...onConflict ? { onConflict } : {},
|
|
12205
|
-
...prune ? { prune: true } : {}
|
|
12931
|
+
...prune ? { prune: true } : {},
|
|
12932
|
+
...guard,
|
|
12933
|
+
...release ? { release } : {}
|
|
12206
12934
|
});
|
|
12935
|
+
if (result.result === "definitionRequired") throw new Error("Server reported definitionRequired for a full-FPO request");
|
|
12936
|
+
if (expectNoChanges && fpoPlanHasDrift(result)) throw new FpoDriftError(result);
|
|
12937
|
+
return result;
|
|
12207
12938
|
}
|
|
12208
12939
|
/**
|
|
12209
12940
|
* Reconstruct a self-contained FPO from the live product graph (resolved by
|
|
@@ -12378,8 +13109,8 @@ function defineSurface(input) {
|
|
|
12378
13109
|
if (typeof input.name !== "string" || input.name.length === 0) throw new Error("defineSurface requires a non-empty string \"name\"");
|
|
12379
13110
|
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
13111
|
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");
|
|
13112
|
+
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");
|
|
13113
|
+
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
13114
|
if (input.status !== void 0 && ![
|
|
12384
13115
|
"draft",
|
|
12385
13116
|
"active",
|
|
@@ -12501,14 +13232,16 @@ async function ensureSurface(client, productId, definition, options = {}) {
|
|
|
12501
13232
|
const localHash = await computeSurfaceContentHash(definition);
|
|
12502
13233
|
const memoKey = `${productId} ${definition.name} ${localHash}`;
|
|
12503
13234
|
const contentHash = memo.get(memoKey) ?? localHash;
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
13235
|
+
if (!(definition.inbound !== void 0 || definition.outbound !== void 0)) {
|
|
13236
|
+
const probe = await request(client, productId, {
|
|
13237
|
+
name: definition.name,
|
|
13238
|
+
contentHash,
|
|
13239
|
+
...passthrough
|
|
13240
|
+
});
|
|
13241
|
+
if (probe.result !== "definitionRequired") {
|
|
13242
|
+
memoize(memo, memoKey, probe);
|
|
13243
|
+
return probe;
|
|
13244
|
+
}
|
|
12512
13245
|
}
|
|
12513
13246
|
const converged = await request(client, productId, {
|
|
12514
13247
|
name: definition.name,
|
|
@@ -12521,8 +13254,8 @@ async function ensureSurface(client, productId, definition, options = {}) {
|
|
|
12521
13254
|
}
|
|
12522
13255
|
/**
|
|
12523
13256
|
* Pull the canonical definition + provenance for a surface by name within a
|
|
12524
|
-
* product — the absorb-drift direction.
|
|
12525
|
-
*
|
|
13257
|
+
* product — the absorb-drift direction. Configured credentials come back as
|
|
13258
|
+
* `'<redacted>'`, which `ensure` accepts to keep the stored value.
|
|
12526
13259
|
*/
|
|
12527
13260
|
async function pullSurface(client, productId, name) {
|
|
12528
13261
|
return client.get(`/products/${encodeURIComponent(productId)}/surfaces/pull`, { name });
|
|
@@ -13237,6 +13970,25 @@ var Runtype = class {
|
|
|
13237
13970
|
return new ToolsNamespace(() => this.getClient());
|
|
13238
13971
|
}
|
|
13239
13972
|
/**
|
|
13973
|
+
* Question sets namespace - Question set config-as-code (define / ensure / pull)
|
|
13974
|
+
*
|
|
13975
|
+
* @example
|
|
13976
|
+
* ```typescript
|
|
13977
|
+
* import { defineQuestionSet, Runtype } from '@runtypelabs/sdk'
|
|
13978
|
+
*
|
|
13979
|
+
* const triage = defineQuestionSet({
|
|
13980
|
+
* slug: 'ticket-triage',
|
|
13981
|
+
* name: 'Ticket triage',
|
|
13982
|
+
* questions: { urgent: { type: 'boolean', instructions: 'Is the ticket urgent?' } },
|
|
13983
|
+
* })
|
|
13984
|
+
*
|
|
13985
|
+
* await Runtype.questionSets.ensure(triage, { release: 'publish' })
|
|
13986
|
+
* ```
|
|
13987
|
+
*/
|
|
13988
|
+
static get questionSets() {
|
|
13989
|
+
return new QuestionSetsNamespace(() => this.getClient());
|
|
13990
|
+
}
|
|
13991
|
+
/**
|
|
13240
13992
|
* Products namespace - Product config-as-code (define / ensure / pull)
|
|
13241
13993
|
*
|
|
13242
13994
|
* Converges the top-level product record (description, icon, spec). Nested
|
|
@@ -13525,7 +14277,7 @@ function transformQueryParams(params) {
|
|
|
13525
14277
|
}
|
|
13526
14278
|
//#endregion
|
|
13527
14279
|
//#region src/version.ts
|
|
13528
|
-
const SDK_VERSION = "10.
|
|
14280
|
+
const SDK_VERSION = "10.11.0";
|
|
13529
14281
|
/**
|
|
13530
14282
|
* Canonical client identity the API uses for attribution
|
|
13531
14283
|
* (`detectActorSource` in apps/api/src/lib/audit-log.ts keys `'sdk'` off the
|
|
@@ -15716,7 +16468,6 @@ const STEP_TYPE_TO_METHOD = {
|
|
|
15716
16468
|
//#endregion
|
|
15717
16469
|
exports.AgentAliasDependencyError = AgentAliasDependencyError;
|
|
15718
16470
|
exports.AgentAliasNotFoundError = AgentAliasNotFoundError;
|
|
15719
|
-
exports.AgentAliasPreviewLimitError = AgentAliasPreviewLimitError;
|
|
15720
16471
|
exports.AgentAliasRevisionMismatchError = AgentAliasRevisionMismatchError;
|
|
15721
16472
|
exports.AgentAliasRevisionRequiredError = AgentAliasRevisionRequiredError;
|
|
15722
16473
|
exports.AgentAliasesNamespace = AgentAliasesNamespace;
|
|
@@ -15741,6 +16492,8 @@ exports.ClientTokenDriftError = ClientTokenDriftError;
|
|
|
15741
16492
|
exports.ClientTokenEnsureError = ClientTokenEnsureError;
|
|
15742
16493
|
exports.ClientTokensEndpoint = ClientTokensEndpoint;
|
|
15743
16494
|
exports.ClientTokensNamespace = ClientTokensNamespace;
|
|
16495
|
+
exports.CollectionDriftError = CollectionDriftError;
|
|
16496
|
+
exports.CollectionEnsureConflictError = CollectionEnsureConflictError;
|
|
15744
16497
|
exports.CollectionsEndpoint = CollectionsEndpoint;
|
|
15745
16498
|
exports.ContextTemplatesEndpoint = ContextTemplatesEndpoint;
|
|
15746
16499
|
exports.ConversationsEndpoint = ConversationsEndpoint;
|
|
@@ -15750,7 +16503,9 @@ exports.DEFAULT_STALL_STOP_AFTER = DEFAULT_STALL_STOP_AFTER;
|
|
|
15750
16503
|
exports.DispatchEndpoint = DispatchEndpoint;
|
|
15751
16504
|
exports.ENSURE_RELEASE_DEPLOY_CONFLICT_MESSAGE = ENSURE_RELEASE_DEPLOY_CONFLICT_MESSAGE;
|
|
15752
16505
|
exports.EvalBuilder = EvalBuilder;
|
|
16506
|
+
exports.EvalDriftError = EvalDriftError;
|
|
15753
16507
|
exports.EvalEndpoint = EvalEndpoint;
|
|
16508
|
+
exports.EvalEnsureConflictError = EvalEnsureConflictError;
|
|
15754
16509
|
exports.EvalRunner = EvalRunner;
|
|
15755
16510
|
exports.EvalSuitesNamespace = EvalSuitesNamespace;
|
|
15756
16511
|
exports.EvalsNamespace = EvalsNamespace;
|
|
@@ -15764,6 +16519,7 @@ exports.FlowStepsEndpoint = FlowStepsEndpoint;
|
|
|
15764
16519
|
exports.FlowVersionsEndpoint = FlowVersionsEndpoint;
|
|
15765
16520
|
exports.FlowsEndpoint = FlowsEndpoint;
|
|
15766
16521
|
exports.FlowsNamespace = FlowsNamespace;
|
|
16522
|
+
exports.FpoDriftError = FpoDriftError;
|
|
15767
16523
|
exports.IntegrationsEndpoint = IntegrationsEndpoint;
|
|
15768
16524
|
exports.LEDGER_ARTIFACT_LINE_PREFIX = LEDGER_ARTIFACT_LINE_PREFIX;
|
|
15769
16525
|
exports.LIVE_AGENT_ALIAS = LIVE_AGENT_ALIAS;
|
|
@@ -15775,6 +16531,9 @@ exports.ProductsNamespace = ProductsNamespace;
|
|
|
15775
16531
|
exports.PromptsEndpoint = PromptsEndpoint;
|
|
15776
16532
|
exports.PromptsNamespace = PromptsNamespace;
|
|
15777
16533
|
exports.ProviderKeysEndpoint = ProviderKeysEndpoint;
|
|
16534
|
+
exports.QuestionSetDriftError = QuestionSetDriftError;
|
|
16535
|
+
exports.QuestionSetEnsureConflictError = QuestionSetEnsureConflictError;
|
|
16536
|
+
exports.QuestionSetsNamespace = QuestionSetsNamespace;
|
|
15778
16537
|
exports.RUNTYPE_CLIENT_KIND = RUNTYPE_CLIENT_KIND;
|
|
15779
16538
|
exports.RecordsEndpoint = RecordsEndpoint;
|
|
15780
16539
|
exports.RunsNamespace = RunsNamespace;
|
|
@@ -15786,6 +16545,8 @@ exports.SDK_USER_AGENT = SDK_USER_AGENT;
|
|
|
15786
16545
|
exports.SDK_VERSION = SDK_VERSION;
|
|
15787
16546
|
exports.STEP_FIELD_REGISTRY = STEP_FIELD_REGISTRY;
|
|
15788
16547
|
exports.STEP_TYPE_TO_METHOD = STEP_TYPE_TO_METHOD;
|
|
16548
|
+
exports.ScheduleDriftError = ScheduleDriftError;
|
|
16549
|
+
exports.ScheduleEnsureConflictError = ScheduleEnsureConflictError;
|
|
15789
16550
|
exports.SchedulesEndpoint = SchedulesEndpoint;
|
|
15790
16551
|
exports.SecretsEndpoint = SecretsEndpoint;
|
|
15791
16552
|
exports.SkillDriftError = SkillDriftError;
|
|
@@ -15825,6 +16586,10 @@ exports.computeEvalContentHash = computeEvalContentHash;
|
|
|
15825
16586
|
exports.computeFlowContentHash = computeFlowContentHash;
|
|
15826
16587
|
exports.computeFpoContentHash = computeFpoContentHash;
|
|
15827
16588
|
exports.computeProductContentHash = computeProductContentHash;
|
|
16589
|
+
exports.computeQuestionSetContentHash = computeQuestionSetContentHash;
|
|
16590
|
+
exports.computeRecordCollectionContentHash = computeRecordCollectionContentHash;
|
|
16591
|
+
exports.computeScheduleContentHash = computeScheduleContentHash;
|
|
16592
|
+
exports.computeScheduleDefinitionHash = computeScheduleDefinitionHash;
|
|
15828
16593
|
exports.computeSkillContentHash = computeSkillContentHash;
|
|
15829
16594
|
exports.computeSurfaceContentHash = computeSurfaceContentHash;
|
|
15830
16595
|
exports.computeToolContentHash = computeToolContentHash;
|
|
@@ -15842,6 +16607,8 @@ exports.defineFlow = defineFlow;
|
|
|
15842
16607
|
exports.defineFpo = defineFpo;
|
|
15843
16608
|
exports.definePlaybook = definePlaybook;
|
|
15844
16609
|
exports.defineProduct = defineProduct;
|
|
16610
|
+
exports.defineQuestionSet = defineQuestionSet;
|
|
16611
|
+
exports.defineSchedule = defineSchedule;
|
|
15845
16612
|
exports.defineSkill = defineSkill;
|
|
15846
16613
|
exports.defineSurface = defineSurface;
|
|
15847
16614
|
exports.defineTool = defineTool;
|
|
@@ -15849,8 +16616,10 @@ exports.deployWorkflow = deployWorkflow;
|
|
|
15849
16616
|
exports.ensureDefaultWorkflowHooks = ensureDefaultWorkflowHooks;
|
|
15850
16617
|
exports.ensureEval = ensureEval;
|
|
15851
16618
|
exports.ensureFpo = ensureFpo;
|
|
16619
|
+
exports.ensureSchedule = ensureSchedule;
|
|
15852
16620
|
exports.evaluateGeneratedRuntimeToolProposal = evaluateGeneratedRuntimeToolProposal;
|
|
15853
16621
|
exports.extractDeclaredToolResultChars = extractDeclaredToolResultChars;
|
|
16622
|
+
exports.fpoPlanHasDrift = fpoPlanHasDrift;
|
|
15854
16623
|
exports.gameWorkflow = gameWorkflow;
|
|
15855
16624
|
exports.getDefaultPlanPath = getDefaultPlanPath;
|
|
15856
16625
|
exports.getLikelySupportingCandidatePaths = getLikelySupportingCandidatePaths;
|
|
@@ -15875,6 +16644,9 @@ exports.normalizeAgentDefinition = normalizeAgentDefinition;
|
|
|
15875
16644
|
exports.normalizeCandidatePath = normalizeCandidatePath;
|
|
15876
16645
|
exports.normalizeFpoDefinition = normalizeFpoDefinition;
|
|
15877
16646
|
exports.normalizeProductDefinition = normalizeProductDefinition;
|
|
16647
|
+
exports.normalizeQuestionSetDefinition = normalizeQuestionSetDefinition;
|
|
16648
|
+
exports.normalizeRecordCollectionDefinition = normalizeRecordCollectionDefinition;
|
|
16649
|
+
exports.normalizeScheduleContent = normalizeScheduleContent;
|
|
15878
16650
|
exports.normalizeSkillDefinition = normalizeSkillDefinition;
|
|
15879
16651
|
exports.normalizeSurfaceDefinition = normalizeSurfaceDefinition;
|
|
15880
16652
|
exports.normalizeToolDefinition = normalizeToolDefinition;
|
|
@@ -15890,6 +16662,7 @@ exports.promoteAgent = promoteAgent;
|
|
|
15890
16662
|
exports.promotionIdempotencyKey = promotionIdempotencyKey;
|
|
15891
16663
|
exports.pullEval = pullEval;
|
|
15892
16664
|
exports.pullFpo = pullFpo;
|
|
16665
|
+
exports.pullSchedule = pullSchedule;
|
|
15893
16666
|
exports.ranStep = ranStep;
|
|
15894
16667
|
exports.regex = regex;
|
|
15895
16668
|
exports.registerWorkflowHook = registerWorkflowHook;
|