@runtypelabs/sdk 5.5.0 → 5.6.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 +998 -912
- package/dist/index.d.cts +464 -375
- package/dist/index.d.ts +464 -375
- package/dist/index.mjs +998 -912
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2073,218 +2073,355 @@ function resolveBatchExecutionId(pausedTools) {
|
|
|
2073
2073
|
return "";
|
|
2074
2074
|
}
|
|
2075
2075
|
|
|
2076
|
-
// src/
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2076
|
+
// src/evals-ensure.ts
|
|
2077
|
+
var CHECK_GRADER_KINDS = /* @__PURE__ */ new Set([
|
|
2078
|
+
"contains",
|
|
2079
|
+
"not_contains",
|
|
2080
|
+
"matches_expected",
|
|
2081
|
+
"regex",
|
|
2082
|
+
"valid_json",
|
|
2083
|
+
"json_field",
|
|
2084
|
+
"length",
|
|
2085
|
+
"latency",
|
|
2086
|
+
"no_error",
|
|
2087
|
+
// Trace checks.
|
|
2088
|
+
"called_tool",
|
|
2089
|
+
"not_called_tool",
|
|
2090
|
+
"used_no_tools",
|
|
2091
|
+
"max_tool_calls",
|
|
2092
|
+
"tool_order",
|
|
2093
|
+
"ran_step",
|
|
2094
|
+
"step_order",
|
|
2095
|
+
"completed",
|
|
2096
|
+
"cost"
|
|
2097
|
+
]);
|
|
2098
|
+
function gradeable(data) {
|
|
2099
|
+
const obj = { ...data };
|
|
2100
|
+
const rebuild = (patch) => gradeable({ ...obj, ...patch });
|
|
2101
|
+
Object.defineProperty(obj, "gate", {
|
|
2102
|
+
value: () => rebuild({ severity: "gate" }),
|
|
2103
|
+
enumerable: false
|
|
2104
|
+
});
|
|
2105
|
+
Object.defineProperty(obj, "soft", {
|
|
2106
|
+
value: () => rebuild({ severity: "soft" }),
|
|
2107
|
+
enumerable: false
|
|
2108
|
+
});
|
|
2109
|
+
if (data.kind === "ai") {
|
|
2110
|
+
Object.defineProperty(obj, "atLeast", {
|
|
2111
|
+
value: (threshold) => {
|
|
2112
|
+
if (typeof threshold !== "number" || !Number.isFinite(threshold) || threshold < 1 || threshold > 5) {
|
|
2113
|
+
throw new Error("atLeast() requires a numeric judge threshold between 1 and 5");
|
|
2092
2114
|
}
|
|
2093
|
-
return
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
}
|
|
2115
|
+
return rebuild({ threshold });
|
|
2116
|
+
},
|
|
2117
|
+
enumerable: false
|
|
2118
|
+
});
|
|
2098
2119
|
}
|
|
2099
|
-
return
|
|
2120
|
+
return obj;
|
|
2100
2121
|
}
|
|
2101
|
-
function
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
...typeof stepObj.when === "string" ? { when: stepObj.when } : {},
|
|
2108
|
-
config: normalizeConfigForHash(stepObj.config),
|
|
2109
|
-
order: typeof stepObj.order === "number" ? stepObj.order : 0
|
|
2110
|
-
};
|
|
2122
|
+
function contains(value, opts) {
|
|
2123
|
+
return gradeable({
|
|
2124
|
+
kind: "contains",
|
|
2125
|
+
value,
|
|
2126
|
+
...opts?.caseSensitive ? { caseSensitive: true } : {}
|
|
2127
|
+
});
|
|
2111
2128
|
}
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
})
|
|
2118
|
-
const serialized = JSON.stringify(normalized);
|
|
2119
|
-
const encoded = new TextEncoder().encode(serialized);
|
|
2120
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
|
|
2121
|
-
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2129
|
+
function notContains(value, opts) {
|
|
2130
|
+
return gradeable({
|
|
2131
|
+
kind: "not_contains",
|
|
2132
|
+
value,
|
|
2133
|
+
...opts?.caseSensitive ? { caseSensitive: true } : {}
|
|
2134
|
+
});
|
|
2122
2135
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
"
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
"
|
|
2131
|
-
|
|
2132
|
-
function
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
const scanKeys = (value, subPath) => {
|
|
2144
|
-
if (!isPlainObject(value)) return;
|
|
2145
|
-
for (const key of Object.keys(value)) {
|
|
2146
|
-
if (isAccountScoped(key)) found.push(`${subPath}.${key}`);
|
|
2147
|
-
}
|
|
2148
|
-
};
|
|
2149
|
-
if (isPlainObject(tools)) {
|
|
2150
|
-
scanArray(tools.toolIds, `${path}.tools.toolIds`);
|
|
2151
|
-
scanKeys(tools.toolConfigs, `${path}.tools.toolConfigs`);
|
|
2152
|
-
scanKeys(tools.perToolLimits, `${path}.tools.perToolLimits`);
|
|
2153
|
-
if (isPlainObject(tools.approval)) {
|
|
2154
|
-
scanArray(tools.approval.require, `${path}.tools.approval.require`);
|
|
2155
|
-
}
|
|
2156
|
-
if (isPlainObject(tools.subagentConfig)) {
|
|
2157
|
-
scanArray(tools.subagentConfig.toolPool, `${path}.tools.subagentConfig.toolPool`);
|
|
2158
|
-
}
|
|
2159
|
-
if (isPlainObject(tools.codeModeConfig)) {
|
|
2160
|
-
scanArray(tools.codeModeConfig.toolPool, `${path}.tools.codeModeConfig.toolPool`);
|
|
2161
|
-
}
|
|
2162
|
-
if (Array.isArray(tools.runtimeTools)) {
|
|
2163
|
-
tools.runtimeTools.forEach((runtimeTool, i) => {
|
|
2164
|
-
if (!isPlainObject(runtimeTool) || !isPlainObject(runtimeTool.config)) return;
|
|
2165
|
-
const base = `${path}.tools.runtimeTools[${i}].config`;
|
|
2166
|
-
const rtConfig = runtimeTool.config;
|
|
2167
|
-
if (runtimeTool.toolType === "subagent" && isRawId(rtConfig.agentId, "agent_")) {
|
|
2168
|
-
found.push(`${base}.agentId`);
|
|
2169
|
-
} else if (runtimeTool.toolType === "flow" && isRawId(rtConfig.flowId, "flow_")) {
|
|
2170
|
-
found.push(`${base}.flowId`);
|
|
2171
|
-
}
|
|
2172
|
-
});
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
if (isAccountScoped(config.toolId)) {
|
|
2176
|
-
found.push(`${path}.toolId`);
|
|
2177
|
-
}
|
|
2178
|
-
if (isRawId(config.agentId, "agent_")) {
|
|
2179
|
-
found.push(`${path}.agentId`);
|
|
2136
|
+
function matchesExpected() {
|
|
2137
|
+
return gradeable({ kind: "matches_expected" });
|
|
2138
|
+
}
|
|
2139
|
+
function regex(pattern, flags) {
|
|
2140
|
+
return gradeable({ kind: "regex", pattern, ...flags ? { flags } : {} });
|
|
2141
|
+
}
|
|
2142
|
+
function validJson() {
|
|
2143
|
+
return gradeable({ kind: "valid_json" });
|
|
2144
|
+
}
|
|
2145
|
+
function jsonField(path, opts) {
|
|
2146
|
+
return gradeable({
|
|
2147
|
+
kind: "json_field",
|
|
2148
|
+
path,
|
|
2149
|
+
...opts && "equals" in opts && opts.equals !== void 0 ? { equals: opts.equals } : {},
|
|
2150
|
+
...opts && typeof opts.exists === "boolean" ? { exists: opts.exists } : {}
|
|
2151
|
+
});
|
|
2152
|
+
}
|
|
2153
|
+
function length(opts) {
|
|
2154
|
+
if (!opts || opts.minChars === void 0 && opts.maxChars === void 0) {
|
|
2155
|
+
throw new Error("length() requires at least one of minChars or maxChars");
|
|
2180
2156
|
}
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
)
|
|
2191
|
-
);
|
|
2192
|
-
}
|
|
2193
|
-
});
|
|
2157
|
+
return gradeable({
|
|
2158
|
+
kind: "length",
|
|
2159
|
+
...opts.minChars !== void 0 ? { minChars: opts.minChars } : {},
|
|
2160
|
+
...opts.maxChars !== void 0 ? { maxChars: opts.maxChars } : {}
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
function latency(maxMs) {
|
|
2164
|
+
if (!Number.isFinite(maxMs) || maxMs <= 0) {
|
|
2165
|
+
throw new Error("latency() requires a positive maxMs");
|
|
2194
2166
|
}
|
|
2195
|
-
return
|
|
2167
|
+
return gradeable({ kind: "latency", maxMs });
|
|
2196
2168
|
}
|
|
2197
|
-
function
|
|
2198
|
-
|
|
2199
|
-
|
|
2169
|
+
function noError() {
|
|
2170
|
+
return gradeable({ kind: "no_error" });
|
|
2171
|
+
}
|
|
2172
|
+
function calledTool(name, opts) {
|
|
2173
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
2174
|
+
throw new Error("calledTool() requires a non-empty tool name");
|
|
2200
2175
|
}
|
|
2201
|
-
if (
|
|
2202
|
-
throw new Error('
|
|
2176
|
+
if (opts?.times !== void 0 && (!Number.isInteger(opts.times) || opts.times <= 0)) {
|
|
2177
|
+
throw new Error('calledTool() "times" must be a positive integer');
|
|
2203
2178
|
}
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2179
|
+
return gradeable({
|
|
2180
|
+
kind: "called_tool",
|
|
2181
|
+
name,
|
|
2182
|
+
...opts && "input" in opts && opts.input !== void 0 ? { input: opts.input } : {},
|
|
2183
|
+
...opts && "output" in opts && opts.output !== void 0 ? { output: opts.output } : {},
|
|
2184
|
+
...opts && typeof opts.isError === "boolean" ? { isError: opts.isError } : {},
|
|
2185
|
+
...opts?.times !== void 0 ? { times: opts.times } : {}
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
function notCalledTool(name) {
|
|
2189
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
2190
|
+
throw new Error("notCalledTool() requires a non-empty tool name");
|
|
2209
2191
|
}
|
|
2210
|
-
|
|
2211
|
-
|
|
2192
|
+
return gradeable({ kind: "not_called_tool", name });
|
|
2193
|
+
}
|
|
2194
|
+
function usedNoTools() {
|
|
2195
|
+
return gradeable({ kind: "used_no_tools" });
|
|
2196
|
+
}
|
|
2197
|
+
function maxToolCalls(max) {
|
|
2198
|
+
if (!Number.isInteger(max) || max < 0) {
|
|
2199
|
+
throw new Error("maxToolCalls() requires a non-negative integer");
|
|
2212
2200
|
}
|
|
2213
|
-
|
|
2214
|
-
if (!isPlainObject(step)) {
|
|
2215
|
-
throw new Error(`defineFlow: steps[${index}] must be an object`);
|
|
2216
|
-
}
|
|
2217
|
-
if (typeof step.type !== "string" || step.type.length === 0) {
|
|
2218
|
-
throw new Error(`defineFlow: steps[${index}] requires a non-empty string "type"`);
|
|
2219
|
-
}
|
|
2220
|
-
if (typeof step.name !== "string" || step.name.length === 0) {
|
|
2221
|
-
throw new Error(`defineFlow: steps[${index}] requires a non-empty string "name"`);
|
|
2222
|
-
}
|
|
2223
|
-
const unknownStepKeys = Object.keys(step).filter((key) => !DEFINE_FLOW_STEP_KEYS.has(key));
|
|
2224
|
-
if (unknownStepKeys.length > 0) {
|
|
2225
|
-
throw new Error(
|
|
2226
|
-
`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.)`
|
|
2227
|
-
);
|
|
2228
|
-
}
|
|
2229
|
-
const config = isPlainObject(step.config) ? step.config : void 0;
|
|
2230
|
-
if (config) {
|
|
2231
|
-
const nonPortable = collectStepNonPortableToolRefs(config, `steps[${index}].config`);
|
|
2232
|
-
if (nonPortable.length > 0) {
|
|
2233
|
-
throw new Error(
|
|
2234
|
-
`defineFlow: account-scoped reference(s) at ${nonPortable.join(", ")}. Definitions must be environment-portable \u2014 tool_\u2026/agent_\u2026/flow_\u2026 IDs belong to one account/environment. Use builtin:/platform:/mcp: references, or reference a saved resource by name \u2014 tool:<name>, agent:<name>, or flow:<name> instead.`
|
|
2235
|
-
);
|
|
2236
|
-
}
|
|
2237
|
-
}
|
|
2238
|
-
return {
|
|
2239
|
-
type: step.type,
|
|
2240
|
-
name: step.name,
|
|
2241
|
-
// Explicit 1-based order (the flow builder's convention) so the local
|
|
2242
|
-
// probe hash agrees with the server's persisted step order.
|
|
2243
|
-
order: typeof step.order === "number" ? step.order : index + 1,
|
|
2244
|
-
...step.enabled !== void 0 ? { enabled: step.enabled } : {},
|
|
2245
|
-
...typeof step.when === "string" ? { when: step.when } : {},
|
|
2246
|
-
...config ? { config } : {}
|
|
2247
|
-
};
|
|
2248
|
-
});
|
|
2249
|
-
return { name: input.name, steps };
|
|
2201
|
+
return gradeable({ kind: "max_tool_calls", max });
|
|
2250
2202
|
}
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
this.name = "FlowEnsureConflictError";
|
|
2255
|
-
this.code = body.code;
|
|
2256
|
-
this.lastModifiedSource = body.lastModifiedSource;
|
|
2257
|
-
this.modifiedAt = body.modifiedAt;
|
|
2258
|
-
this.currentHash = body.currentHash;
|
|
2203
|
+
function toolOrder(tools) {
|
|
2204
|
+
if (!Array.isArray(tools) || tools.length === 0) {
|
|
2205
|
+
throw new Error("toolOrder() requires a non-empty array of tool names");
|
|
2259
2206
|
}
|
|
2260
|
-
};
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
);
|
|
2266
|
-
this.name = "FlowDriftError";
|
|
2267
|
-
this.plan = plan;
|
|
2207
|
+
return gradeable({ kind: "tool_order", tools });
|
|
2208
|
+
}
|
|
2209
|
+
function ranStep(name) {
|
|
2210
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
2211
|
+
throw new Error("ranStep() requires a non-empty step name");
|
|
2268
2212
|
}
|
|
2269
|
-
};
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
try {
|
|
2275
|
-
return { status: Number(match[1]), body: JSON.parse(match[2]) };
|
|
2276
|
-
} catch {
|
|
2277
|
-
return { status: Number(match[1]), body: null };
|
|
2213
|
+
return gradeable({ kind: "ran_step", name });
|
|
2214
|
+
}
|
|
2215
|
+
function stepOrder(steps) {
|
|
2216
|
+
if (!Array.isArray(steps) || steps.length === 0) {
|
|
2217
|
+
throw new Error("stepOrder() requires a non-empty array of step names");
|
|
2278
2218
|
}
|
|
2219
|
+
return gradeable({ kind: "step_order", steps });
|
|
2279
2220
|
}
|
|
2280
|
-
function
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
if (
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
);
|
|
2221
|
+
function completed() {
|
|
2222
|
+
return gradeable({ kind: "completed" });
|
|
2223
|
+
}
|
|
2224
|
+
function cost(maxUsd) {
|
|
2225
|
+
if (!Number.isFinite(maxUsd) || maxUsd <= 0) {
|
|
2226
|
+
throw new Error("cost() requires a positive maxUsd");
|
|
2227
|
+
}
|
|
2228
|
+
return gradeable({ kind: "cost", maxUsd });
|
|
2229
|
+
}
|
|
2230
|
+
function judge(criteria, opts) {
|
|
2231
|
+
if (typeof criteria !== "string" || criteria.trim().length === 0) {
|
|
2232
|
+
throw new Error("judge() requires non-empty criteria");
|
|
2233
|
+
}
|
|
2234
|
+
return gradeable({
|
|
2235
|
+
kind: "ai",
|
|
2236
|
+
criteria,
|
|
2237
|
+
...opts?.preset ? { preset: opts.preset } : {},
|
|
2238
|
+
...opts?.useExpected ? { useExpected: true } : {},
|
|
2239
|
+
...opts?.model ? { model: opts.model } : {},
|
|
2240
|
+
...opts?.threshold !== void 0 ? { threshold: opts.threshold } : {}
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
var judges = {
|
|
2244
|
+
answersQuestion: () => judge(
|
|
2245
|
+
"The response directly addresses what the user asked, without dodging or answering a different question.",
|
|
2246
|
+
{ preset: "answersQuestion" }
|
|
2247
|
+
),
|
|
2248
|
+
matchesExpected: () => judge(
|
|
2249
|
+
"The response conveys the same facts and conclusion as the expected answer. Wording may differ.",
|
|
2250
|
+
{ preset: "matchesExpected", useExpected: true }
|
|
2251
|
+
),
|
|
2252
|
+
followsInstructions: () => judge(
|
|
2253
|
+
"The response obeys every instruction in the system prompt (format, tone, constraints, refusals).",
|
|
2254
|
+
{ preset: "followsInstructions" }
|
|
2255
|
+
),
|
|
2256
|
+
grounded: () => judge(
|
|
2257
|
+
"Every factual claim in the response is supported by the provided context or the expected answer. Flag anything invented.",
|
|
2258
|
+
{ preset: "grounded" }
|
|
2259
|
+
),
|
|
2260
|
+
rightTone: (voice = "{describe the voice you want}") => judge(`The response matches this voice: ${voice}.`, { preset: "rightTone" }),
|
|
2261
|
+
safeToSend: () => judge(
|
|
2262
|
+
"The response contains nothing embarrassing to show a customer: no leaked internals, no hostile tone, no policy violations.",
|
|
2263
|
+
{ preset: "safeToSend" }
|
|
2264
|
+
)
|
|
2265
|
+
};
|
|
2266
|
+
var DEFINE_EVAL_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
2267
|
+
"name",
|
|
2268
|
+
"target",
|
|
2269
|
+
"graders",
|
|
2270
|
+
"cases",
|
|
2271
|
+
"virtual"
|
|
2272
|
+
]);
|
|
2273
|
+
var DEFINE_EVAL_CASE_KEYS = /* @__PURE__ */ new Set(["name", "input", "expected", "expect"]);
|
|
2274
|
+
function isPlainObject(value) {
|
|
2275
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2276
|
+
}
|
|
2277
|
+
function normalizeTarget(target) {
|
|
2278
|
+
if (!isPlainObject(target)) {
|
|
2279
|
+
throw new Error('defineEval requires a "target" object: { flow: name } or { agent: name }');
|
|
2280
|
+
}
|
|
2281
|
+
const hasFlow = typeof target.flow === "string" && target.flow.length > 0;
|
|
2282
|
+
const hasAgent = typeof target.agent === "string" && target.agent.length > 0;
|
|
2283
|
+
if (hasFlow === hasAgent) {
|
|
2284
|
+
throw new Error(
|
|
2285
|
+
'defineEval "target" must name exactly one of flow or agent: { flow: "name" } XOR { agent: "name" }'
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
const extraKeys = Object.keys(target).filter((k) => k !== "flow" && k !== "agent");
|
|
2289
|
+
if (extraKeys.length > 0) {
|
|
2290
|
+
throw new Error(`defineEval "target" has unknown field(s): ${extraKeys.join(", ")}`);
|
|
2291
|
+
}
|
|
2292
|
+
return hasFlow ? { flow: target.flow } : { agent: target.agent };
|
|
2293
|
+
}
|
|
2294
|
+
function validateGrader(grader, where) {
|
|
2295
|
+
if (!isPlainObject(grader) || typeof grader.kind !== "string") {
|
|
2296
|
+
throw new Error(`defineEval: ${where} must be a grader object with a string "kind"`);
|
|
2297
|
+
}
|
|
2298
|
+
if (grader.kind === "ai") {
|
|
2299
|
+
if (typeof grader.criteria !== "string" || grader.criteria.trim().length === 0) {
|
|
2300
|
+
throw new Error(`defineEval: ${where} is an AI grader and requires non-empty "criteria"`);
|
|
2301
|
+
}
|
|
2302
|
+
return grader;
|
|
2303
|
+
}
|
|
2304
|
+
if (!CHECK_GRADER_KINDS.has(grader.kind)) {
|
|
2305
|
+
throw new Error(
|
|
2306
|
+
`defineEval: ${where} has unknown grader kind "${grader.kind}". Known kinds: ${[...CHECK_GRADER_KINDS].join(", ")}, ai.`
|
|
2307
|
+
);
|
|
2308
|
+
}
|
|
2309
|
+
return grader;
|
|
2310
|
+
}
|
|
2311
|
+
function normalizeCaseInput(input, where) {
|
|
2312
|
+
if (input === void 0) return {};
|
|
2313
|
+
if (!isPlainObject(input)) {
|
|
2314
|
+
throw new Error(`defineEval: ${where} "input" must be an object`);
|
|
2315
|
+
}
|
|
2316
|
+
const out = {};
|
|
2317
|
+
if (input.variables !== void 0) {
|
|
2318
|
+
if (!isPlainObject(input.variables)) {
|
|
2319
|
+
throw new Error(`defineEval: ${where} "input.variables" must be an object`);
|
|
2320
|
+
}
|
|
2321
|
+
out.variables = input.variables;
|
|
2322
|
+
}
|
|
2323
|
+
if (input.messages !== void 0) {
|
|
2324
|
+
if (!Array.isArray(input.messages)) {
|
|
2325
|
+
throw new Error(`defineEval: ${where} "input.messages" must be an array`);
|
|
2326
|
+
}
|
|
2327
|
+
out.messages = input.messages.map((m, i) => {
|
|
2328
|
+
if (!isPlainObject(m) || typeof m.role !== "string" || typeof m.content !== "string") {
|
|
2329
|
+
throw new Error(`defineEval: ${where} "input.messages[${i}]" must be { role, content }`);
|
|
2330
|
+
}
|
|
2331
|
+
return { role: m.role, content: m.content };
|
|
2332
|
+
});
|
|
2333
|
+
}
|
|
2334
|
+
return out;
|
|
2335
|
+
}
|
|
2336
|
+
function defineEval(input) {
|
|
2337
|
+
if (!input || typeof input !== "object") {
|
|
2338
|
+
throw new Error("defineEval requires a definition object");
|
|
2339
|
+
}
|
|
2340
|
+
const unknownKeys = Object.keys(input).filter((k) => !DEFINE_EVAL_TOP_LEVEL_KEYS.has(k));
|
|
2341
|
+
if (unknownKeys.length > 0) {
|
|
2342
|
+
throw new Error(
|
|
2343
|
+
`defineEval: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are target, graders, cases, virtual.`
|
|
2344
|
+
);
|
|
2345
|
+
}
|
|
2346
|
+
const target = normalizeTarget(input.target);
|
|
2347
|
+
if (input.name !== void 0 && (typeof input.name !== "string" || input.name.length === 0)) {
|
|
2348
|
+
throw new Error('defineEval "name" must be a non-empty string when provided');
|
|
2349
|
+
}
|
|
2350
|
+
const name = input.name ?? ("flow" in target ? `flow:${target.flow}` : `agent:${target.agent}`);
|
|
2351
|
+
const suiteGraders = (input.graders ?? []).map((g, i) => validateGrader(g, `graders[${i}]`));
|
|
2352
|
+
if (!Array.isArray(input.cases) || input.cases.length === 0) {
|
|
2353
|
+
throw new Error('defineEval requires a non-empty "cases" array');
|
|
2354
|
+
}
|
|
2355
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
2356
|
+
const cases = input.cases.map((c, index) => {
|
|
2357
|
+
if (!isPlainObject(c)) {
|
|
2358
|
+
throw new Error(`defineEval: cases[${index}] must be an object`);
|
|
2359
|
+
}
|
|
2360
|
+
if (typeof c.name !== "string" || c.name.length === 0) {
|
|
2361
|
+
throw new Error(`defineEval: cases[${index}] requires a non-empty string "name"`);
|
|
2362
|
+
}
|
|
2363
|
+
if (seenNames.has(c.name)) {
|
|
2364
|
+
throw new Error(`defineEval: duplicate case name "${c.name}" (case names are the identity)`);
|
|
2365
|
+
}
|
|
2366
|
+
seenNames.add(c.name);
|
|
2367
|
+
const unknownCaseKeys = Object.keys(c).filter((k) => !DEFINE_EVAL_CASE_KEYS.has(k));
|
|
2368
|
+
if (unknownCaseKeys.length > 0) {
|
|
2369
|
+
throw new Error(
|
|
2370
|
+
`defineEval: cases[${index}] ("${c.name}") has unknown field(s): ${unknownCaseKeys.join(
|
|
2371
|
+
", "
|
|
2372
|
+
)}. Allowed case fields are name, input, expected, expect.`
|
|
2373
|
+
);
|
|
2374
|
+
}
|
|
2375
|
+
const caseGraders = (c.expect ?? []).map(
|
|
2376
|
+
(g, i) => validateGrader(g, `cases[${index}].expect[${i}]`)
|
|
2377
|
+
);
|
|
2378
|
+
const expect = [...suiteGraders, ...caseGraders];
|
|
2379
|
+
if (expect.length === 0) {
|
|
2380
|
+
throw new Error(
|
|
2381
|
+
`defineEval: cases[${index}] ("${c.name}") has no graders. Add suite-level "graders" or case-level "expect" so there is something to score.`
|
|
2382
|
+
);
|
|
2383
|
+
}
|
|
2384
|
+
if (c.expected !== void 0 && !isPlainObject(c.expected)) {
|
|
2385
|
+
throw new Error(`defineEval: cases[${index}] ("${c.name}") "expected" must be an object`);
|
|
2386
|
+
}
|
|
2387
|
+
return {
|
|
2388
|
+
name: c.name,
|
|
2389
|
+
input: normalizeCaseInput(c.input, `cases[${index}] ("${c.name}")`),
|
|
2390
|
+
...c.expected !== void 0 ? { expected: c.expected } : {},
|
|
2391
|
+
expect
|
|
2392
|
+
};
|
|
2393
|
+
});
|
|
2394
|
+
return { name, target, cases, virtual: input.virtual === true };
|
|
2395
|
+
}
|
|
2396
|
+
function normalizeForHash(value) {
|
|
2397
|
+
if (Array.isArray(value)) return value.map(normalizeForHash);
|
|
2398
|
+
if (isPlainObject(value)) {
|
|
2399
|
+
const out = {};
|
|
2400
|
+
for (const key of Object.keys(value).sort()) {
|
|
2401
|
+
const v = value[key];
|
|
2402
|
+
if (v === void 0) continue;
|
|
2403
|
+
out[key] = normalizeForHash(v);
|
|
2404
|
+
}
|
|
2405
|
+
return out;
|
|
2406
|
+
}
|
|
2407
|
+
return value;
|
|
2408
|
+
}
|
|
2409
|
+
async function computeEvalContentHash(definition) {
|
|
2410
|
+
const canonical = {
|
|
2411
|
+
target: normalizeForHash(definition.target),
|
|
2412
|
+
virtual: definition.virtual,
|
|
2413
|
+
cases: [...definition.cases].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0).map((c) => ({
|
|
2414
|
+
name: c.name,
|
|
2415
|
+
input: normalizeForHash(c.input),
|
|
2416
|
+
...c.expected !== void 0 ? { expected: normalizeForHash(c.expected) } : {},
|
|
2417
|
+
// Grader order preserved on purpose (it maps to the result index).
|
|
2418
|
+
expect: c.expect.map((g) => normalizeForHash(g))
|
|
2419
|
+
}))
|
|
2420
|
+
};
|
|
2421
|
+
const serialized = JSON.stringify(canonical);
|
|
2422
|
+
const encoded = new TextEncoder().encode(serialized);
|
|
2423
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
|
|
2424
|
+
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2288
2425
|
}
|
|
2289
2426
|
var serverHashMemo = /* @__PURE__ */ new WeakMap();
|
|
2290
2427
|
function memoFor(client) {
|
|
@@ -2295,261 +2432,572 @@ function memoFor(client) {
|
|
|
2295
2432
|
}
|
|
2296
2433
|
return memo;
|
|
2297
2434
|
}
|
|
2298
|
-
function
|
|
2299
|
-
if (
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
try {
|
|
2303
|
-
return await client.post(
|
|
2304
|
-
"/flows/ensure",
|
|
2305
|
-
body
|
|
2435
|
+
async function ensureEval(client, definition) {
|
|
2436
|
+
if (definition.virtual) {
|
|
2437
|
+
throw new Error(
|
|
2438
|
+
"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."
|
|
2306
2439
|
);
|
|
2307
|
-
} catch (err) {
|
|
2308
|
-
const conflict = toConflictError(err);
|
|
2309
|
-
if (conflict) throw conflict;
|
|
2310
|
-
throw err;
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
async function ensureFlow(client, definition, options = {}) {
|
|
2314
|
-
const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
|
|
2315
|
-
const passthrough = {
|
|
2316
|
-
...onConflict ? { onConflict } : {},
|
|
2317
|
-
...release ? { release } : {},
|
|
2318
|
-
...expectedRemoteHash ? { expectedRemoteHash } : {}
|
|
2319
|
-
};
|
|
2320
|
-
if (dryRun || expectNoChanges) {
|
|
2321
|
-
const plan = await request(client, {
|
|
2322
|
-
name: definition.name,
|
|
2323
|
-
definition,
|
|
2324
|
-
dryRun: true,
|
|
2325
|
-
...passthrough
|
|
2326
|
-
});
|
|
2327
|
-
if (plan.result !== "plan") {
|
|
2328
|
-
throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
|
|
2329
|
-
}
|
|
2330
|
-
if (expectNoChanges && plan.changes !== "none") {
|
|
2331
|
-
throw new FlowDriftError(plan);
|
|
2332
|
-
}
|
|
2333
|
-
return plan;
|
|
2334
2440
|
}
|
|
2335
2441
|
const memo = memoFor(client);
|
|
2336
|
-
const localHash = await
|
|
2442
|
+
const localHash = await computeEvalContentHash(definition);
|
|
2337
2443
|
const memoKey = `${definition.name} ${localHash}`;
|
|
2338
2444
|
const contentHash = memo.get(memoKey) ?? localHash;
|
|
2339
|
-
const probe = await
|
|
2340
|
-
|
|
2341
|
-
contentHash
|
|
2342
|
-
|
|
2343
|
-
});
|
|
2445
|
+
const probe = await client.post(
|
|
2446
|
+
"/eval/ensure",
|
|
2447
|
+
{ name: definition.name, contentHash }
|
|
2448
|
+
);
|
|
2344
2449
|
if (probe.result !== "definitionRequired") {
|
|
2345
|
-
|
|
2450
|
+
memo.set(memoKey, probe.contentHash);
|
|
2346
2451
|
return probe;
|
|
2347
2452
|
}
|
|
2348
|
-
const converged = await
|
|
2349
|
-
|
|
2350
|
-
definition,
|
|
2351
|
-
|
|
2352
|
-
});
|
|
2453
|
+
const converged = await client.post(
|
|
2454
|
+
"/eval/ensure",
|
|
2455
|
+
{ name: definition.name, definition }
|
|
2456
|
+
);
|
|
2353
2457
|
if (converged.result === "definitionRequired") {
|
|
2354
2458
|
throw new Error("Server reported definitionRequired for a full-definition request");
|
|
2355
2459
|
}
|
|
2356
|
-
|
|
2460
|
+
memo.set(memoKey, converged.contentHash);
|
|
2357
2461
|
return converged;
|
|
2358
2462
|
}
|
|
2359
|
-
async function
|
|
2360
|
-
return client.get("/
|
|
2463
|
+
async function pullEval(client, name) {
|
|
2464
|
+
return client.get("/eval/pull", { name });
|
|
2465
|
+
}
|
|
2466
|
+
async function runEvalSuite(client, input) {
|
|
2467
|
+
return client.post("/eval/run", input);
|
|
2361
2468
|
}
|
|
2362
2469
|
|
|
2363
|
-
// src/flows-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
* ```
|
|
2386
|
-
*/
|
|
2387
|
-
upsert(config) {
|
|
2388
|
-
return new RuntypeFlowBuilder(this.getClient, "upsert", config);
|
|
2470
|
+
// src/flows-ensure.ts
|
|
2471
|
+
function isPlainObject2(value) {
|
|
2472
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2473
|
+
}
|
|
2474
|
+
function normalizeConfigForHash(config) {
|
|
2475
|
+
if (!isPlainObject2(config)) return {};
|
|
2476
|
+
const normalized = {};
|
|
2477
|
+
for (const key of Object.keys(config).sort()) {
|
|
2478
|
+
const value = config[key];
|
|
2479
|
+
if (value === void 0) continue;
|
|
2480
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
2481
|
+
normalized[key] = normalizeConfigForHash(value);
|
|
2482
|
+
} else if (Array.isArray(value)) {
|
|
2483
|
+
normalized[key] = value.map((item) => {
|
|
2484
|
+
if (item !== null && typeof item === "object" && !Array.isArray(item)) {
|
|
2485
|
+
return normalizeConfigForHash(item);
|
|
2486
|
+
}
|
|
2487
|
+
return item;
|
|
2488
|
+
});
|
|
2489
|
+
} else {
|
|
2490
|
+
normalized[key] = value;
|
|
2491
|
+
}
|
|
2389
2492
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2493
|
+
return normalized;
|
|
2494
|
+
}
|
|
2495
|
+
function normalizeStepForHash(step) {
|
|
2496
|
+
const stepObj = isPlainObject2(step) ? step : {};
|
|
2497
|
+
return {
|
|
2498
|
+
type: typeof stepObj.type === "string" ? stepObj.type : "",
|
|
2499
|
+
name: typeof stepObj.name === "string" ? stepObj.name : "",
|
|
2500
|
+
enabled: stepObj.enabled !== false,
|
|
2501
|
+
...typeof stepObj.when === "string" ? { when: stepObj.when } : {},
|
|
2502
|
+
config: normalizeConfigForHash(stepObj.config),
|
|
2503
|
+
order: typeof stepObj.order === "number" ? stepObj.order : 0
|
|
2504
|
+
};
|
|
2505
|
+
}
|
|
2506
|
+
async function computeFlowContentHash(steps) {
|
|
2507
|
+
const normalized = [...steps].sort((a, b) => {
|
|
2508
|
+
const orderA = isPlainObject2(a) && typeof a.order === "number" ? a.order : 0;
|
|
2509
|
+
const orderB = isPlainObject2(b) && typeof b.order === "number" ? b.order : 0;
|
|
2510
|
+
return orderA - orderB;
|
|
2511
|
+
}).map(normalizeStepForHash);
|
|
2512
|
+
const serialized = JSON.stringify(normalized);
|
|
2513
|
+
const encoded = new TextEncoder().encode(serialized);
|
|
2514
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
|
|
2515
|
+
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2516
|
+
}
|
|
2517
|
+
var DEFINE_FLOW_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["name", "steps", "evals"]);
|
|
2518
|
+
var DEFINE_FLOW_STEP_KEYS = /* @__PURE__ */ new Set([
|
|
2519
|
+
"type",
|
|
2520
|
+
"name",
|
|
2521
|
+
"order",
|
|
2522
|
+
"enabled",
|
|
2523
|
+
"when",
|
|
2524
|
+
"config"
|
|
2525
|
+
]);
|
|
2526
|
+
function collectStepNonPortableToolRefs(config, path) {
|
|
2527
|
+
const found = [];
|
|
2528
|
+
const tools = config.tools;
|
|
2529
|
+
const isAccountScoped = (ref) => typeof ref === "string" && ref.startsWith("tool_");
|
|
2530
|
+
const isRawId = (ref, prefix) => typeof ref === "string" && ref.startsWith(prefix);
|
|
2531
|
+
const scanArray = (value, subPath) => {
|
|
2532
|
+
if (!Array.isArray(value)) return;
|
|
2533
|
+
value.forEach((ref, i) => {
|
|
2534
|
+
if (isAccountScoped(ref)) found.push(`${subPath}[${i}]`);
|
|
2535
|
+
});
|
|
2536
|
+
};
|
|
2537
|
+
const scanKeys = (value, subPath) => {
|
|
2538
|
+
if (!isPlainObject2(value)) return;
|
|
2539
|
+
for (const key of Object.keys(value)) {
|
|
2540
|
+
if (isAccountScoped(key)) found.push(`${subPath}.${key}`);
|
|
2541
|
+
}
|
|
2542
|
+
};
|
|
2543
|
+
if (isPlainObject2(tools)) {
|
|
2544
|
+
scanArray(tools.toolIds, `${path}.tools.toolIds`);
|
|
2545
|
+
scanKeys(tools.toolConfigs, `${path}.tools.toolConfigs`);
|
|
2546
|
+
scanKeys(tools.perToolLimits, `${path}.tools.perToolLimits`);
|
|
2547
|
+
if (isPlainObject2(tools.approval)) {
|
|
2548
|
+
scanArray(tools.approval.require, `${path}.tools.approval.require`);
|
|
2549
|
+
}
|
|
2550
|
+
if (isPlainObject2(tools.subagentConfig)) {
|
|
2551
|
+
scanArray(tools.subagentConfig.toolPool, `${path}.tools.subagentConfig.toolPool`);
|
|
2552
|
+
}
|
|
2553
|
+
if (isPlainObject2(tools.codeModeConfig)) {
|
|
2554
|
+
scanArray(tools.codeModeConfig.toolPool, `${path}.tools.codeModeConfig.toolPool`);
|
|
2555
|
+
}
|
|
2556
|
+
if (Array.isArray(tools.runtimeTools)) {
|
|
2557
|
+
tools.runtimeTools.forEach((runtimeTool, i) => {
|
|
2558
|
+
if (!isPlainObject2(runtimeTool) || !isPlainObject2(runtimeTool.config)) return;
|
|
2559
|
+
const base = `${path}.tools.runtimeTools[${i}].config`;
|
|
2560
|
+
const rtConfig = runtimeTool.config;
|
|
2561
|
+
if (runtimeTool.toolType === "subagent" && isRawId(rtConfig.agentId, "agent_")) {
|
|
2562
|
+
found.push(`${base}.agentId`);
|
|
2563
|
+
} else if (runtimeTool.toolType === "flow" && isRawId(rtConfig.flowId, "flow_")) {
|
|
2564
|
+
found.push(`${base}.flowId`);
|
|
2565
|
+
}
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2409
2568
|
}
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
* absorb-drift direction of the ensure protocol.
|
|
2413
|
-
*/
|
|
2414
|
-
async pull(name) {
|
|
2415
|
-
return pullFlow(this.getClient(), name);
|
|
2569
|
+
if (isAccountScoped(config.toolId)) {
|
|
2570
|
+
found.push(`${path}.toolId`);
|
|
2416
2571
|
}
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
*
|
|
2420
|
-
* Use for temporary or ad-hoc flow execution.
|
|
2421
|
-
*
|
|
2422
|
-
* @example
|
|
2423
|
-
* ```typescript
|
|
2424
|
-
* const result = await Runtype.flows.virtual({ name: 'Temp Flow' })
|
|
2425
|
-
* .prompt({ name: 'Process', model: 'gpt-4o', userPrompt: '...' })
|
|
2426
|
-
* .stream()
|
|
2427
|
-
* ```
|
|
2428
|
-
*/
|
|
2429
|
-
virtual(config) {
|
|
2430
|
-
return new RuntypeFlowBuilder(this.getClient, "virtual", config);
|
|
2572
|
+
if (isRawId(config.agentId, "agent_")) {
|
|
2573
|
+
found.push(`${path}.agentId`);
|
|
2431
2574
|
}
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
use(flowId) {
|
|
2443
|
-
return new RuntypeFlowBuilder(this.getClient, "existing", void 0, flowId);
|
|
2575
|
+
for (const branch of ["trueSteps", "falseSteps"]) {
|
|
2576
|
+
const nested = config[branch];
|
|
2577
|
+
if (!Array.isArray(nested)) continue;
|
|
2578
|
+
nested.forEach((nestedStep, i) => {
|
|
2579
|
+
if (isPlainObject2(nestedStep) && isPlainObject2(nestedStep.config)) {
|
|
2580
|
+
found.push(
|
|
2581
|
+
...collectStepNonPortableToolRefs(nestedStep.config, `${path}.${branch}[${i}].config`)
|
|
2582
|
+
);
|
|
2583
|
+
}
|
|
2584
|
+
});
|
|
2444
2585
|
}
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
* const result = await Runtype.flows.execute('flow_123', {
|
|
2451
|
-
* record: { name: 'Test', type: 'data' },
|
|
2452
|
-
* streamResponse: true
|
|
2453
|
-
* })
|
|
2454
|
-
* ```
|
|
2455
|
-
*/
|
|
2456
|
-
async execute(flowId, options) {
|
|
2457
|
-
const builder = this.use(flowId);
|
|
2458
|
-
if (options?.record) builder.withRecord(options.record);
|
|
2459
|
-
if (options?.messages) builder.withMessages(options.messages);
|
|
2460
|
-
return options?.streamResponse !== false ? builder.stream() : builder.result();
|
|
2586
|
+
return found;
|
|
2587
|
+
}
|
|
2588
|
+
function defineFlow(input) {
|
|
2589
|
+
if (!input || typeof input !== "object") {
|
|
2590
|
+
throw new Error("defineFlow requires a definition object");
|
|
2461
2591
|
}
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
constructor(getClient, mode, config, flowId) {
|
|
2465
|
-
this.getClient = getClient;
|
|
2466
|
-
this.steps = [];
|
|
2467
|
-
this.stepCounter = 0;
|
|
2468
|
-
this.upsertOptions = {};
|
|
2469
|
-
this.dispatchOptions = {};
|
|
2470
|
-
this.mode = mode;
|
|
2471
|
-
if (mode === "existing" && flowId) {
|
|
2472
|
-
this.existingFlowId = flowId;
|
|
2473
|
-
this.flowConfig = { name: "" };
|
|
2474
|
-
} else if (config) {
|
|
2475
|
-
const { createVersionOnChange, allowOverwriteExternalChanges, ...flowConfig } = config;
|
|
2476
|
-
this.flowConfig = flowConfig;
|
|
2477
|
-
if (mode === "upsert") {
|
|
2478
|
-
this.upsertOptions = {
|
|
2479
|
-
createVersionOnChange: createVersionOnChange ?? true,
|
|
2480
|
-
...allowOverwriteExternalChanges !== void 0 && { allowOverwriteExternalChanges }
|
|
2481
|
-
};
|
|
2482
|
-
}
|
|
2483
|
-
} else {
|
|
2484
|
-
this.flowConfig = { name: "Untitled Flow" };
|
|
2485
|
-
}
|
|
2592
|
+
if (typeof input.name !== "string" || input.name.length === 0) {
|
|
2593
|
+
throw new Error('defineFlow requires a non-empty string "name"');
|
|
2486
2594
|
}
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
*/
|
|
2493
|
-
withRecord(config) {
|
|
2494
|
-
this.recordConfig = config;
|
|
2495
|
-
return this;
|
|
2595
|
+
const unknownKeys = Object.keys(input).filter((key) => !DEFINE_FLOW_TOP_LEVEL_KEYS.has(key));
|
|
2596
|
+
if (unknownKeys.length > 0) {
|
|
2597
|
+
throw new Error(
|
|
2598
|
+
`defineFlow: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are name, steps, and evals. (Description is not part of the v1 ensure surface.)`
|
|
2599
|
+
);
|
|
2496
2600
|
}
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2601
|
+
if (!Array.isArray(input.steps) || input.steps.length === 0) {
|
|
2602
|
+
throw new Error('defineFlow requires a non-empty "steps" array');
|
|
2603
|
+
}
|
|
2604
|
+
const steps = input.steps.map((step, index) => {
|
|
2605
|
+
if (!isPlainObject2(step)) {
|
|
2606
|
+
throw new Error(`defineFlow: steps[${index}] must be an object`);
|
|
2607
|
+
}
|
|
2608
|
+
if (typeof step.type !== "string" || step.type.length === 0) {
|
|
2609
|
+
throw new Error(`defineFlow: steps[${index}] requires a non-empty string "type"`);
|
|
2610
|
+
}
|
|
2611
|
+
if (typeof step.name !== "string" || step.name.length === 0) {
|
|
2612
|
+
throw new Error(`defineFlow: steps[${index}] requires a non-empty string "name"`);
|
|
2613
|
+
}
|
|
2614
|
+
const unknownStepKeys = Object.keys(step).filter((key) => !DEFINE_FLOW_STEP_KEYS.has(key));
|
|
2615
|
+
if (unknownStepKeys.length > 0) {
|
|
2616
|
+
throw new Error(
|
|
2617
|
+
`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.)`
|
|
2618
|
+
);
|
|
2619
|
+
}
|
|
2620
|
+
const config = isPlainObject2(step.config) ? step.config : void 0;
|
|
2621
|
+
if (config) {
|
|
2622
|
+
const nonPortable = collectStepNonPortableToolRefs(config, `steps[${index}].config`);
|
|
2623
|
+
if (nonPortable.length > 0) {
|
|
2624
|
+
throw new Error(
|
|
2625
|
+
`defineFlow: account-scoped reference(s) at ${nonPortable.join(", ")}. Definitions must be environment-portable \u2014 tool_\u2026/agent_\u2026/flow_\u2026 IDs belong to one account/environment. Use builtin:/platform:/mcp: references, or reference a saved resource by name \u2014 tool:<name>, agent:<name>, or flow:<name> instead.`
|
|
2626
|
+
);
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
return {
|
|
2630
|
+
type: step.type,
|
|
2631
|
+
name: step.name,
|
|
2632
|
+
// Explicit 1-based order (the flow builder's convention) so the local
|
|
2633
|
+
// probe hash agrees with the server's persisted step order.
|
|
2634
|
+
order: typeof step.order === "number" ? step.order : index + 1,
|
|
2635
|
+
...step.enabled !== void 0 ? { enabled: step.enabled } : {},
|
|
2636
|
+
...typeof step.when === "string" ? { when: step.when } : {},
|
|
2637
|
+
...config ? { config } : {}
|
|
2638
|
+
};
|
|
2639
|
+
});
|
|
2640
|
+
let evals;
|
|
2641
|
+
if (input.evals !== void 0) {
|
|
2642
|
+
if (!Array.isArray(input.evals)) {
|
|
2643
|
+
throw new Error('defineFlow: "evals" must be an array');
|
|
2644
|
+
}
|
|
2645
|
+
const seenEvalNames = /* @__PURE__ */ new Set();
|
|
2646
|
+
evals = input.evals.map((evalInput, i) => {
|
|
2647
|
+
if (!isPlainObject2(evalInput)) {
|
|
2648
|
+
throw new Error(`defineFlow: evals[${i}] must be an object`);
|
|
2649
|
+
}
|
|
2650
|
+
if (evalInput.virtual === true) {
|
|
2651
|
+
throw new Error(
|
|
2652
|
+
`defineFlow: evals[${i}] cannot be virtual (inline evals converge with the flow; run a virtual eval directly instead).`
|
|
2653
|
+
);
|
|
2654
|
+
}
|
|
2655
|
+
const withTarget = evalInput.target === void 0 ? { ...evalInput, target: { flow: input.name } } : evalInput;
|
|
2656
|
+
let def;
|
|
2657
|
+
try {
|
|
2658
|
+
def = defineEval(withTarget);
|
|
2659
|
+
} catch (err) {
|
|
2660
|
+
throw new Error(
|
|
2661
|
+
`defineFlow: evals[${i}] \u2014 ${err instanceof Error ? err.message : String(err)}`,
|
|
2662
|
+
{ cause: err }
|
|
2663
|
+
);
|
|
2664
|
+
}
|
|
2665
|
+
if (seenEvalNames.has(def.name)) {
|
|
2666
|
+
throw new Error(
|
|
2667
|
+
`defineFlow: evals[${i}] resolves to the duplicate suite name "${def.name}". Inline eval suites must have distinct names \u2014 give each a \`name\` (two unnamed evals targeting the same flow both default to the same name and would overwrite each other).`
|
|
2668
|
+
);
|
|
2669
|
+
}
|
|
2670
|
+
seenEvalNames.add(def.name);
|
|
2671
|
+
return def;
|
|
2672
|
+
});
|
|
2673
|
+
}
|
|
2674
|
+
return {
|
|
2675
|
+
name: input.name,
|
|
2676
|
+
steps,
|
|
2677
|
+
...evals && evals.length > 0 ? { evals } : {}
|
|
2678
|
+
};
|
|
2679
|
+
}
|
|
2680
|
+
var FlowEnsureConflictError = class extends Error {
|
|
2681
|
+
constructor(body) {
|
|
2682
|
+
super(body.error ?? `Flow ensure conflict: ${body.code}`);
|
|
2683
|
+
this.name = "FlowEnsureConflictError";
|
|
2684
|
+
this.code = body.code;
|
|
2685
|
+
this.lastModifiedSource = body.lastModifiedSource;
|
|
2686
|
+
this.modifiedAt = body.modifiedAt;
|
|
2687
|
+
this.currentHash = body.currentHash;
|
|
2688
|
+
}
|
|
2689
|
+
};
|
|
2690
|
+
var FlowDriftError = class extends Error {
|
|
2691
|
+
constructor(plan) {
|
|
2692
|
+
super(
|
|
2693
|
+
`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.`
|
|
2694
|
+
);
|
|
2695
|
+
this.name = "FlowDriftError";
|
|
2696
|
+
this.plan = plan;
|
|
2697
|
+
}
|
|
2698
|
+
};
|
|
2699
|
+
function parseRequestError(err) {
|
|
2700
|
+
if (!(err instanceof Error)) return { status: null, body: null };
|
|
2701
|
+
const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
|
|
2702
|
+
if (!match) return { status: null, body: null };
|
|
2703
|
+
try {
|
|
2704
|
+
return { status: Number(match[1]), body: JSON.parse(match[2]) };
|
|
2705
|
+
} catch {
|
|
2706
|
+
return { status: Number(match[1]), body: null };
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
function toConflictError(err) {
|
|
2710
|
+
const { status, body } = parseRequestError(err);
|
|
2711
|
+
if (status !== 409 || !isPlainObject2(body)) return null;
|
|
2712
|
+
const code = body.code;
|
|
2713
|
+
if (code !== "external_modification" && code !== "remote_changed") return null;
|
|
2714
|
+
return new FlowEnsureConflictError(
|
|
2715
|
+
body
|
|
2716
|
+
);
|
|
2717
|
+
}
|
|
2718
|
+
var serverHashMemo2 = /* @__PURE__ */ new WeakMap();
|
|
2719
|
+
function memoFor2(client) {
|
|
2720
|
+
let memo = serverHashMemo2.get(client);
|
|
2721
|
+
if (!memo) {
|
|
2722
|
+
memo = /* @__PURE__ */ new Map();
|
|
2723
|
+
serverHashMemo2.set(client, memo);
|
|
2724
|
+
}
|
|
2725
|
+
return memo;
|
|
2726
|
+
}
|
|
2727
|
+
function memoize(memo, memoKey, result) {
|
|
2728
|
+
if (result.result !== "plan") memo.set(memoKey, result.contentHash);
|
|
2729
|
+
}
|
|
2730
|
+
async function request(client, body) {
|
|
2731
|
+
try {
|
|
2732
|
+
return await client.post(
|
|
2733
|
+
"/flows/ensure",
|
|
2734
|
+
body
|
|
2735
|
+
);
|
|
2736
|
+
} catch (err) {
|
|
2737
|
+
const conflict = toConflictError(err);
|
|
2738
|
+
if (conflict) throw conflict;
|
|
2739
|
+
throw err;
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
async function ensureFlow(client, definition, options = {}) {
|
|
2743
|
+
const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
|
|
2744
|
+
const passthrough = {
|
|
2745
|
+
...onConflict ? { onConflict } : {},
|
|
2746
|
+
...release ? { release } : {},
|
|
2747
|
+
...expectedRemoteHash ? { expectedRemoteHash } : {}
|
|
2748
|
+
};
|
|
2749
|
+
const wireDefinition = { name: definition.name, steps: definition.steps };
|
|
2750
|
+
if (dryRun || expectNoChanges) {
|
|
2751
|
+
const plan = await request(client, {
|
|
2752
|
+
name: definition.name,
|
|
2753
|
+
definition: wireDefinition,
|
|
2754
|
+
dryRun: true,
|
|
2755
|
+
...passthrough
|
|
2756
|
+
});
|
|
2757
|
+
if (plan.result !== "plan") {
|
|
2758
|
+
throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
|
|
2759
|
+
}
|
|
2760
|
+
if (expectNoChanges && plan.changes !== "none") {
|
|
2761
|
+
throw new FlowDriftError(plan);
|
|
2762
|
+
}
|
|
2763
|
+
return plan;
|
|
2764
|
+
}
|
|
2765
|
+
const memo = memoFor2(client);
|
|
2766
|
+
const localHash = await computeFlowContentHash(definition.steps);
|
|
2767
|
+
const memoKey = `${definition.name} ${localHash}`;
|
|
2768
|
+
const contentHash = memo.get(memoKey) ?? localHash;
|
|
2769
|
+
const probe = await request(client, {
|
|
2770
|
+
name: definition.name,
|
|
2771
|
+
contentHash,
|
|
2772
|
+
...passthrough
|
|
2773
|
+
});
|
|
2774
|
+
if (probe.result !== "definitionRequired") {
|
|
2775
|
+
memoize(memo, memoKey, probe);
|
|
2776
|
+
return convergeInlineEvals(client, definition, probe);
|
|
2777
|
+
}
|
|
2778
|
+
const converged = await request(client, {
|
|
2779
|
+
name: definition.name,
|
|
2780
|
+
definition: wireDefinition,
|
|
2781
|
+
...passthrough
|
|
2782
|
+
});
|
|
2783
|
+
if (converged.result === "definitionRequired") {
|
|
2784
|
+
throw new Error("Server reported definitionRequired for a full-definition request");
|
|
2785
|
+
}
|
|
2786
|
+
memoize(memo, memoKey, converged);
|
|
2787
|
+
return convergeInlineEvals(client, definition, converged);
|
|
2788
|
+
}
|
|
2789
|
+
async function convergeInlineEvals(client, definition, result) {
|
|
2790
|
+
if (result.result === "plan" || !definition.evals?.length) {
|
|
2791
|
+
return result;
|
|
2792
|
+
}
|
|
2793
|
+
const evals = [];
|
|
2794
|
+
for (const evalDef of definition.evals) {
|
|
2795
|
+
evals.push(await ensureEval(client, evalDef));
|
|
2796
|
+
}
|
|
2797
|
+
return { ...result, evals };
|
|
2798
|
+
}
|
|
2799
|
+
async function pullFlow(client, name) {
|
|
2800
|
+
return client.get("/flows/pull", { name });
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
// src/flows-namespace.ts
|
|
2804
|
+
var FlowsNamespace = class {
|
|
2805
|
+
constructor(getClient) {
|
|
2806
|
+
this.getClient = getClient;
|
|
2503
2807
|
}
|
|
2504
2808
|
/**
|
|
2505
|
-
*
|
|
2809
|
+
* Create or update a flow by name (upsert mode)
|
|
2810
|
+
*
|
|
2811
|
+
* The recommended pattern for code-first flow management when you want to
|
|
2812
|
+
* save AND run in one dispatch. For a deploy-time, non-executing converge
|
|
2813
|
+
* (CI/CD config-as-code), use {@link ensure} instead — upsert and ensure
|
|
2814
|
+
* are siblings, not versions of each other: upsert is the runtime verb
|
|
2815
|
+
* (save-and-run), ensure is the deploy verb (converge only).
|
|
2816
|
+
*
|
|
2817
|
+
* @example
|
|
2818
|
+
* ```typescript
|
|
2819
|
+
* const result = await Runtype.flows.upsert({
|
|
2820
|
+
* name: 'My Flow',
|
|
2821
|
+
* createVersionOnChange: true
|
|
2822
|
+
* })
|
|
2823
|
+
* .prompt({ name: 'Analyze', model: 'gpt-4o', userPrompt: '...' })
|
|
2824
|
+
* .stream()
|
|
2825
|
+
* ```
|
|
2506
2826
|
*/
|
|
2507
|
-
|
|
2508
|
-
this.
|
|
2509
|
-
return this;
|
|
2827
|
+
upsert(config) {
|
|
2828
|
+
return new RuntypeFlowBuilder(this.getClient, "upsert", config);
|
|
2510
2829
|
}
|
|
2511
2830
|
/**
|
|
2512
|
-
*
|
|
2831
|
+
* Idempotently converge a `defineFlow` definition onto the platform —
|
|
2832
|
+
* the deploy-time, non-executing sibling of {@link upsert}. Hash-first:
|
|
2833
|
+
* the steady state is one tiny probe request. Creates an immutable version
|
|
2834
|
+
* snapshot on every change; never deletes; never executes the flow.
|
|
2835
|
+
*
|
|
2836
|
+
* When the definition carries inline `evals`, each suite is converged via
|
|
2837
|
+
* `/eval/ensure` after the flow itself (real converge path only — not on
|
|
2838
|
+
* dryRun/`expectNoChanges`), and the outcomes are returned as `result.evals`.
|
|
2839
|
+
*
|
|
2840
|
+
* @example
|
|
2841
|
+
* ```typescript
|
|
2842
|
+
* const def = defineFlow({
|
|
2843
|
+
* name: 'Onboarding Digest',
|
|
2844
|
+
* steps: [...],
|
|
2845
|
+
* evals: [{ cases: [{ name: 'smoke', input: {...}, expect: [contains('ok')] }] }],
|
|
2846
|
+
* })
|
|
2847
|
+
*
|
|
2848
|
+
* // Converge the flow AND its inline eval suites (CI/deploy).
|
|
2849
|
+
* const result = await Runtype.flows.ensure(def)
|
|
2850
|
+
*
|
|
2851
|
+
* // PR drift gate.
|
|
2852
|
+
* await Runtype.flows.ensure(def, { expectNoChanges: true })
|
|
2853
|
+
* ```
|
|
2513
2854
|
*/
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
return this;
|
|
2855
|
+
async ensure(definition, options = {}) {
|
|
2856
|
+
return ensureFlow(this.getClient(), definition, options);
|
|
2517
2857
|
}
|
|
2518
2858
|
/**
|
|
2519
|
-
*
|
|
2520
|
-
*
|
|
2521
|
-
* step types before a dedicated SDK convenience method exists.
|
|
2859
|
+
* Pull the canonical definition + provenance for a flow by name — the
|
|
2860
|
+
* absorb-drift direction of the ensure protocol.
|
|
2522
2861
|
*/
|
|
2523
|
-
|
|
2524
|
-
this.
|
|
2525
|
-
config.type,
|
|
2526
|
-
config.name || config.type,
|
|
2527
|
-
config.config || {},
|
|
2528
|
-
config.enabled,
|
|
2529
|
-
config.when
|
|
2530
|
-
);
|
|
2531
|
-
return this;
|
|
2862
|
+
async pull(name) {
|
|
2863
|
+
return pullFlow(this.getClient(), name);
|
|
2532
2864
|
}
|
|
2533
|
-
// ============================================================================
|
|
2534
|
-
// Step Methods
|
|
2535
|
-
// ============================================================================
|
|
2536
2865
|
/**
|
|
2537
|
-
*
|
|
2866
|
+
* Create a virtual flow (one-off, not saved)
|
|
2867
|
+
*
|
|
2868
|
+
* Use for temporary or ad-hoc flow execution.
|
|
2869
|
+
*
|
|
2870
|
+
* @example
|
|
2871
|
+
* ```typescript
|
|
2872
|
+
* const result = await Runtype.flows.virtual({ name: 'Temp Flow' })
|
|
2873
|
+
* .prompt({ name: 'Process', model: 'gpt-4o', userPrompt: '...' })
|
|
2874
|
+
* .stream()
|
|
2875
|
+
* ```
|
|
2538
2876
|
*/
|
|
2539
|
-
|
|
2540
|
-
this.
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2877
|
+
virtual(config) {
|
|
2878
|
+
return new RuntypeFlowBuilder(this.getClient, "virtual", config);
|
|
2879
|
+
}
|
|
2880
|
+
/**
|
|
2881
|
+
* Use an existing flow by ID
|
|
2882
|
+
*
|
|
2883
|
+
* @example
|
|
2884
|
+
* ```typescript
|
|
2885
|
+
* const result = await Runtype.flows.use('flow_123')
|
|
2886
|
+
* .withRecord({ name: 'Test', type: 'data' })
|
|
2887
|
+
* .stream()
|
|
2888
|
+
* ```
|
|
2889
|
+
*/
|
|
2890
|
+
use(flowId) {
|
|
2891
|
+
return new RuntypeFlowBuilder(this.getClient, "existing", void 0, flowId);
|
|
2892
|
+
}
|
|
2893
|
+
/**
|
|
2894
|
+
* Quick execution of an existing flow
|
|
2895
|
+
*
|
|
2896
|
+
* @example
|
|
2897
|
+
* ```typescript
|
|
2898
|
+
* const result = await Runtype.flows.execute('flow_123', {
|
|
2899
|
+
* record: { name: 'Test', type: 'data' },
|
|
2900
|
+
* streamResponse: true
|
|
2901
|
+
* })
|
|
2902
|
+
* ```
|
|
2903
|
+
*/
|
|
2904
|
+
async execute(flowId, options) {
|
|
2905
|
+
const builder = this.use(flowId);
|
|
2906
|
+
if (options?.record) builder.withRecord(options.record);
|
|
2907
|
+
if (options?.messages) builder.withMessages(options.messages);
|
|
2908
|
+
return options?.streamResponse !== false ? builder.stream() : builder.result();
|
|
2909
|
+
}
|
|
2910
|
+
};
|
|
2911
|
+
var RuntypeFlowBuilder = class {
|
|
2912
|
+
constructor(getClient, mode, config, flowId) {
|
|
2913
|
+
this.getClient = getClient;
|
|
2914
|
+
this.steps = [];
|
|
2915
|
+
this.stepCounter = 0;
|
|
2916
|
+
this.upsertOptions = {};
|
|
2917
|
+
this.dispatchOptions = {};
|
|
2918
|
+
this.mode = mode;
|
|
2919
|
+
if (mode === "existing" && flowId) {
|
|
2920
|
+
this.existingFlowId = flowId;
|
|
2921
|
+
this.flowConfig = { name: "" };
|
|
2922
|
+
} else if (config) {
|
|
2923
|
+
const { createVersionOnChange, allowOverwriteExternalChanges, ...flowConfig } = config;
|
|
2924
|
+
this.flowConfig = flowConfig;
|
|
2925
|
+
if (mode === "upsert") {
|
|
2926
|
+
this.upsertOptions = {
|
|
2927
|
+
createVersionOnChange: createVersionOnChange ?? true,
|
|
2928
|
+
...allowOverwriteExternalChanges !== void 0 && { allowOverwriteExternalChanges }
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
} else {
|
|
2932
|
+
this.flowConfig = { name: "Untitled Flow" };
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
// ============================================================================
|
|
2936
|
+
// Configuration Methods
|
|
2937
|
+
// ============================================================================
|
|
2938
|
+
/**
|
|
2939
|
+
* Set the record configuration
|
|
2940
|
+
*/
|
|
2941
|
+
withRecord(config) {
|
|
2942
|
+
this.recordConfig = config;
|
|
2943
|
+
return this;
|
|
2944
|
+
}
|
|
2945
|
+
/**
|
|
2946
|
+
* Set conversation messages
|
|
2947
|
+
*/
|
|
2948
|
+
withMessages(messages) {
|
|
2949
|
+
this.messagesConfig = messages;
|
|
2950
|
+
return this;
|
|
2951
|
+
}
|
|
2952
|
+
/**
|
|
2953
|
+
* Set top-level input variables accessible as {{varName}} in templates.
|
|
2954
|
+
*/
|
|
2955
|
+
withInputs(inputs) {
|
|
2956
|
+
this.inputsConfig = inputs;
|
|
2957
|
+
return this;
|
|
2958
|
+
}
|
|
2959
|
+
/**
|
|
2960
|
+
* Set dispatch options
|
|
2961
|
+
*/
|
|
2962
|
+
withOptions(options) {
|
|
2963
|
+
this.dispatchOptions = { ...this.dispatchOptions, ...options };
|
|
2964
|
+
return this;
|
|
2965
|
+
}
|
|
2966
|
+
/**
|
|
2967
|
+
* Add a generic flow step. Prefer the typed helper methods when available;
|
|
2968
|
+
* this escape hatch keeps code-first flows compatible with newer dashboard/API
|
|
2969
|
+
* step types before a dedicated SDK convenience method exists.
|
|
2970
|
+
*/
|
|
2971
|
+
step(config) {
|
|
2972
|
+
this.addStep(
|
|
2973
|
+
config.type,
|
|
2974
|
+
config.name || config.type,
|
|
2975
|
+
config.config || {},
|
|
2976
|
+
config.enabled,
|
|
2977
|
+
config.when
|
|
2978
|
+
);
|
|
2979
|
+
return this;
|
|
2980
|
+
}
|
|
2981
|
+
// ============================================================================
|
|
2982
|
+
// Step Methods
|
|
2983
|
+
// ============================================================================
|
|
2984
|
+
/**
|
|
2985
|
+
* Add a prompt step
|
|
2986
|
+
*/
|
|
2987
|
+
prompt(config) {
|
|
2988
|
+
this.addStep(
|
|
2989
|
+
"prompt",
|
|
2990
|
+
config.name,
|
|
2991
|
+
{
|
|
2992
|
+
model: config.model,
|
|
2993
|
+
userPrompt: config.userPrompt,
|
|
2994
|
+
text: config.userPrompt,
|
|
2995
|
+
systemPrompt: config.systemPrompt,
|
|
2996
|
+
previousMessages: config.previousMessages,
|
|
2997
|
+
outputVariable: config.outputVariable,
|
|
2998
|
+
mode: config.mode,
|
|
2999
|
+
responseFormat: config.responseFormat,
|
|
3000
|
+
temperature: config.temperature,
|
|
2553
3001
|
topP: config.topP,
|
|
2554
3002
|
topK: config.topK,
|
|
2555
3003
|
frequencyPenalty: config.frequencyPenalty,
|
|
@@ -3313,516 +3761,154 @@ var RuntypeFlowBuilder = class {
|
|
|
3313
3761
|
return await client.dispatch(hashOnlyConfig);
|
|
3314
3762
|
} catch (err) {
|
|
3315
3763
|
const is422 = err != null && typeof err === "object" && "statusCode" in err && err.statusCode === 422 || err instanceof Error && /\b422\b/.test(err.message);
|
|
3316
|
-
if (!is422) {
|
|
3317
|
-
throw err;
|
|
3318
|
-
}
|
|
3319
|
-
}
|
|
3320
|
-
const fullConfig = {
|
|
3321
|
-
...config,
|
|
3322
|
-
flow: { ...config.flow, contentHash }
|
|
3323
|
-
};
|
|
3324
|
-
return client.dispatch(fullConfig);
|
|
3325
|
-
}
|
|
3326
|
-
async computeContentHash() {
|
|
3327
|
-
return computeFlowContentHash(this.steps);
|
|
3328
|
-
}
|
|
3329
|
-
addRawStep(type, config) {
|
|
3330
|
-
const { name, enabled, when, ...stepConfig } = config;
|
|
3331
|
-
this.addStep(type, name, stepConfig, enabled, when);
|
|
3332
|
-
return this;
|
|
3333
|
-
}
|
|
3334
|
-
addStep(type, name, config, enabled = true, when) {
|
|
3335
|
-
this.stepCounter++;
|
|
3336
|
-
const cleanConfig = {};
|
|
3337
|
-
for (const [key, value] of Object.entries(config)) {
|
|
3338
|
-
if (value !== void 0) {
|
|
3339
|
-
cleanConfig[key] = value;
|
|
3340
|
-
}
|
|
3341
|
-
}
|
|
3342
|
-
this.steps.push({
|
|
3343
|
-
id: `step-${this.stepCounter}`,
|
|
3344
|
-
type,
|
|
3345
|
-
name,
|
|
3346
|
-
order: this.stepCounter,
|
|
3347
|
-
enabled,
|
|
3348
|
-
...when ? { when } : {},
|
|
3349
|
-
config: cleanConfig
|
|
3350
|
-
});
|
|
3351
|
-
}
|
|
3352
|
-
};
|
|
3353
|
-
|
|
3354
|
-
// src/batches-namespace.ts
|
|
3355
|
-
var BatchesNamespace = class {
|
|
3356
|
-
constructor(getClient) {
|
|
3357
|
-
this.getClient = getClient;
|
|
3358
|
-
}
|
|
3359
|
-
/**
|
|
3360
|
-
* Schedule a batch operation
|
|
3361
|
-
*
|
|
3362
|
-
* Creates and schedules a batch to run a flow on all records of a type.
|
|
3363
|
-
* By default, runs immediately. Use `at` to schedule for a specific time.
|
|
3364
|
-
*
|
|
3365
|
-
* @example
|
|
3366
|
-
* ```typescript
|
|
3367
|
-
* // Run immediately
|
|
3368
|
-
* const batch = await Runtype.batches.schedule({
|
|
3369
|
-
* flowId: 'flow_123',
|
|
3370
|
-
* recordType: 'customers',
|
|
3371
|
-
* })
|
|
3372
|
-
*
|
|
3373
|
-
* // Schedule for later
|
|
3374
|
-
* const batch = await Runtype.batches.schedule({
|
|
3375
|
-
* flowId: 'flow_123',
|
|
3376
|
-
* recordType: 'customers',
|
|
3377
|
-
* at: new Date('2024-01-15T09:00:00Z'),
|
|
3378
|
-
* })
|
|
3379
|
-
*
|
|
3380
|
-
* // With options
|
|
3381
|
-
* const batch = await Runtype.batches.schedule({
|
|
3382
|
-
* flowId: 'flow_123',
|
|
3383
|
-
* recordType: 'customers',
|
|
3384
|
-
* concurrency: 5,
|
|
3385
|
-
* continueOnError: true,
|
|
3386
|
-
* filter: { status: 'active' },
|
|
3387
|
-
* limit: 100,
|
|
3388
|
-
* })
|
|
3389
|
-
* ```
|
|
3390
|
-
*/
|
|
3391
|
-
async schedule(config) {
|
|
3392
|
-
const client = this.getClient();
|
|
3393
|
-
const payload = {
|
|
3394
|
-
flowId: config.flowId,
|
|
3395
|
-
recordType: config.recordType
|
|
3396
|
-
};
|
|
3397
|
-
if (config.at) {
|
|
3398
|
-
payload.scheduledAt = config.at.toISOString();
|
|
3399
|
-
}
|
|
3400
|
-
const options = {};
|
|
3401
|
-
if (config.async !== void 0) options.async = config.async;
|
|
3402
|
-
if (config.concurrency !== void 0) options.concurrency = config.concurrency;
|
|
3403
|
-
if (config.continueOnError !== void 0) options.continueOnError = config.continueOnError;
|
|
3404
|
-
if (config.storeResults !== void 0) options.storeResults = config.storeResults;
|
|
3405
|
-
if (config.modelOverride !== void 0) options.modelOverride = config.modelOverride;
|
|
3406
|
-
if (Object.keys(options).length > 0) {
|
|
3407
|
-
payload.options = options;
|
|
3408
|
-
}
|
|
3409
|
-
if (config.filter) {
|
|
3410
|
-
payload.filter = config.filter;
|
|
3411
|
-
}
|
|
3412
|
-
if (config.limit !== void 0) {
|
|
3413
|
-
payload.limit = config.limit;
|
|
3414
|
-
}
|
|
3415
|
-
return client.post("/batches", payload);
|
|
3416
|
-
}
|
|
3417
|
-
/**
|
|
3418
|
-
* Get batch status by ID
|
|
3419
|
-
*
|
|
3420
|
-
* @example
|
|
3421
|
-
* ```typescript
|
|
3422
|
-
* const status = await Runtype.batches.get('batch_456')
|
|
3423
|
-
* console.log(status.status, status.processedRecords, '/', status.totalRecords)
|
|
3424
|
-
* ```
|
|
3425
|
-
*/
|
|
3426
|
-
async get(batchId) {
|
|
3427
|
-
const client = this.getClient();
|
|
3428
|
-
return client.get(`/batches/${batchId}`);
|
|
3429
|
-
}
|
|
3430
|
-
/**
|
|
3431
|
-
* Cancel a batch operation
|
|
3432
|
-
*
|
|
3433
|
-
* Cancels a queued or running batch. Records already processed are not rolled back.
|
|
3434
|
-
*
|
|
3435
|
-
* @example
|
|
3436
|
-
* ```typescript
|
|
3437
|
-
* await Runtype.batches.cancel('batch_456')
|
|
3438
|
-
* ```
|
|
3439
|
-
*/
|
|
3440
|
-
async cancel(batchId) {
|
|
3441
|
-
const client = this.getClient();
|
|
3442
|
-
return client.post(`/batches/${batchId}/cancel`);
|
|
3443
|
-
}
|
|
3444
|
-
/**
|
|
3445
|
-
* List batch operations
|
|
3446
|
-
*
|
|
3447
|
-
* @example
|
|
3448
|
-
* ```typescript
|
|
3449
|
-
* // List all batches
|
|
3450
|
-
* const batches = await Runtype.batches.list()
|
|
3451
|
-
*
|
|
3452
|
-
* // Filter by status
|
|
3453
|
-
* const running = await Runtype.batches.list({ status: 'running' })
|
|
3454
|
-
*
|
|
3455
|
-
* // Filter by flow
|
|
3456
|
-
* const flowBatches = await Runtype.batches.list({ flowId: 'flow_123' })
|
|
3457
|
-
* ```
|
|
3458
|
-
*/
|
|
3459
|
-
async list(params) {
|
|
3460
|
-
const client = this.getClient();
|
|
3461
|
-
return client.get("/batches", params);
|
|
3462
|
-
}
|
|
3463
|
-
};
|
|
3464
|
-
|
|
3465
|
-
// src/evals-ensure.ts
|
|
3466
|
-
var CHECK_GRADER_KINDS = /* @__PURE__ */ new Set([
|
|
3467
|
-
"contains",
|
|
3468
|
-
"not_contains",
|
|
3469
|
-
"matches_expected",
|
|
3470
|
-
"regex",
|
|
3471
|
-
"valid_json",
|
|
3472
|
-
"json_field",
|
|
3473
|
-
"length",
|
|
3474
|
-
"latency",
|
|
3475
|
-
"no_error",
|
|
3476
|
-
// Trace checks.
|
|
3477
|
-
"called_tool",
|
|
3478
|
-
"not_called_tool",
|
|
3479
|
-
"used_no_tools",
|
|
3480
|
-
"max_tool_calls",
|
|
3481
|
-
"tool_order",
|
|
3482
|
-
"ran_step",
|
|
3483
|
-
"step_order",
|
|
3484
|
-
"completed",
|
|
3485
|
-
"cost"
|
|
3486
|
-
]);
|
|
3487
|
-
function contains(value, opts) {
|
|
3488
|
-
return { kind: "contains", value, ...opts?.caseSensitive ? { caseSensitive: true } : {} };
|
|
3489
|
-
}
|
|
3490
|
-
function notContains(value, opts) {
|
|
3491
|
-
return { kind: "not_contains", value, ...opts?.caseSensitive ? { caseSensitive: true } : {} };
|
|
3492
|
-
}
|
|
3493
|
-
function matchesExpected() {
|
|
3494
|
-
return { kind: "matches_expected" };
|
|
3495
|
-
}
|
|
3496
|
-
function regex(pattern, flags) {
|
|
3497
|
-
return { kind: "regex", pattern, ...flags ? { flags } : {} };
|
|
3498
|
-
}
|
|
3499
|
-
function validJson() {
|
|
3500
|
-
return { kind: "valid_json" };
|
|
3501
|
-
}
|
|
3502
|
-
function jsonField(path, opts) {
|
|
3503
|
-
return {
|
|
3504
|
-
kind: "json_field",
|
|
3505
|
-
path,
|
|
3506
|
-
...opts && "equals" in opts && opts.equals !== void 0 ? { equals: opts.equals } : {},
|
|
3507
|
-
...opts && typeof opts.exists === "boolean" ? { exists: opts.exists } : {}
|
|
3508
|
-
};
|
|
3509
|
-
}
|
|
3510
|
-
function length(opts) {
|
|
3511
|
-
if (!opts || opts.minChars === void 0 && opts.maxChars === void 0) {
|
|
3512
|
-
throw new Error("length() requires at least one of minChars or maxChars");
|
|
3513
|
-
}
|
|
3514
|
-
return {
|
|
3515
|
-
kind: "length",
|
|
3516
|
-
...opts.minChars !== void 0 ? { minChars: opts.minChars } : {},
|
|
3517
|
-
...opts.maxChars !== void 0 ? { maxChars: opts.maxChars } : {}
|
|
3518
|
-
};
|
|
3519
|
-
}
|
|
3520
|
-
function latency(maxMs) {
|
|
3521
|
-
if (!Number.isFinite(maxMs) || maxMs <= 0) {
|
|
3522
|
-
throw new Error("latency() requires a positive maxMs");
|
|
3523
|
-
}
|
|
3524
|
-
return { kind: "latency", maxMs };
|
|
3525
|
-
}
|
|
3526
|
-
function noError() {
|
|
3527
|
-
return { kind: "no_error" };
|
|
3528
|
-
}
|
|
3529
|
-
function calledTool(name, opts) {
|
|
3530
|
-
if (typeof name !== "string" || name.length === 0) {
|
|
3531
|
-
throw new Error("calledTool() requires a non-empty tool name");
|
|
3532
|
-
}
|
|
3533
|
-
if (opts?.times !== void 0 && (!Number.isInteger(opts.times) || opts.times <= 0)) {
|
|
3534
|
-
throw new Error('calledTool() "times" must be a positive integer');
|
|
3535
|
-
}
|
|
3536
|
-
return {
|
|
3537
|
-
kind: "called_tool",
|
|
3538
|
-
name,
|
|
3539
|
-
...opts && "input" in opts && opts.input !== void 0 ? { input: opts.input } : {},
|
|
3540
|
-
...opts && "output" in opts && opts.output !== void 0 ? { output: opts.output } : {},
|
|
3541
|
-
...opts && typeof opts.isError === "boolean" ? { isError: opts.isError } : {},
|
|
3542
|
-
...opts?.times !== void 0 ? { times: opts.times } : {}
|
|
3543
|
-
};
|
|
3544
|
-
}
|
|
3545
|
-
function notCalledTool(name) {
|
|
3546
|
-
if (typeof name !== "string" || name.length === 0) {
|
|
3547
|
-
throw new Error("notCalledTool() requires a non-empty tool name");
|
|
3548
|
-
}
|
|
3549
|
-
return { kind: "not_called_tool", name };
|
|
3550
|
-
}
|
|
3551
|
-
function usedNoTools() {
|
|
3552
|
-
return { kind: "used_no_tools" };
|
|
3553
|
-
}
|
|
3554
|
-
function maxToolCalls(max) {
|
|
3555
|
-
if (!Number.isInteger(max) || max < 0) {
|
|
3556
|
-
throw new Error("maxToolCalls() requires a non-negative integer");
|
|
3557
|
-
}
|
|
3558
|
-
return { kind: "max_tool_calls", max };
|
|
3559
|
-
}
|
|
3560
|
-
function toolOrder(tools) {
|
|
3561
|
-
if (!Array.isArray(tools) || tools.length === 0) {
|
|
3562
|
-
throw new Error("toolOrder() requires a non-empty array of tool names");
|
|
3563
|
-
}
|
|
3564
|
-
return { kind: "tool_order", tools };
|
|
3565
|
-
}
|
|
3566
|
-
function ranStep(name) {
|
|
3567
|
-
if (typeof name !== "string" || name.length === 0) {
|
|
3568
|
-
throw new Error("ranStep() requires a non-empty step name");
|
|
3569
|
-
}
|
|
3570
|
-
return { kind: "ran_step", name };
|
|
3571
|
-
}
|
|
3572
|
-
function stepOrder(steps) {
|
|
3573
|
-
if (!Array.isArray(steps) || steps.length === 0) {
|
|
3574
|
-
throw new Error("stepOrder() requires a non-empty array of step names");
|
|
3575
|
-
}
|
|
3576
|
-
return { kind: "step_order", steps };
|
|
3577
|
-
}
|
|
3578
|
-
function completed() {
|
|
3579
|
-
return { kind: "completed" };
|
|
3580
|
-
}
|
|
3581
|
-
function cost(maxUsd) {
|
|
3582
|
-
if (!Number.isFinite(maxUsd) || maxUsd <= 0) {
|
|
3583
|
-
throw new Error("cost() requires a positive maxUsd");
|
|
3584
|
-
}
|
|
3585
|
-
return { kind: "cost", maxUsd };
|
|
3586
|
-
}
|
|
3587
|
-
function judge(criteria, opts) {
|
|
3588
|
-
if (typeof criteria !== "string" || criteria.trim().length === 0) {
|
|
3589
|
-
throw new Error("judge() requires non-empty criteria");
|
|
3590
|
-
}
|
|
3591
|
-
return {
|
|
3592
|
-
kind: "ai",
|
|
3593
|
-
criteria,
|
|
3594
|
-
...opts?.preset ? { preset: opts.preset } : {},
|
|
3595
|
-
...opts?.useExpected ? { useExpected: true } : {},
|
|
3596
|
-
...opts?.model ? { model: opts.model } : {},
|
|
3597
|
-
...opts?.threshold !== void 0 ? { threshold: opts.threshold } : {}
|
|
3598
|
-
};
|
|
3599
|
-
}
|
|
3600
|
-
var judges = {
|
|
3601
|
-
answersQuestion: () => judge(
|
|
3602
|
-
"The response directly addresses what the user asked, without dodging or answering a different question.",
|
|
3603
|
-
{ preset: "answersQuestion" }
|
|
3604
|
-
),
|
|
3605
|
-
matchesExpected: () => judge(
|
|
3606
|
-
"The response conveys the same facts and conclusion as the expected answer. Wording may differ.",
|
|
3607
|
-
{ preset: "matchesExpected", useExpected: true }
|
|
3608
|
-
),
|
|
3609
|
-
followsInstructions: () => judge(
|
|
3610
|
-
"The response obeys every instruction in the system prompt (format, tone, constraints, refusals).",
|
|
3611
|
-
{ preset: "followsInstructions" }
|
|
3612
|
-
),
|
|
3613
|
-
grounded: () => judge(
|
|
3614
|
-
"Every factual claim in the response is supported by the provided context or the expected answer. Flag anything invented.",
|
|
3615
|
-
{ preset: "grounded" }
|
|
3616
|
-
),
|
|
3617
|
-
rightTone: (voice = "{describe the voice you want}") => judge(`The response matches this voice: ${voice}.`, { preset: "rightTone" }),
|
|
3618
|
-
safeToSend: () => judge(
|
|
3619
|
-
"The response contains nothing embarrassing to show a customer: no leaked internals, no hostile tone, no policy violations.",
|
|
3620
|
-
{ preset: "safeToSend" }
|
|
3621
|
-
)
|
|
3622
|
-
};
|
|
3623
|
-
var DEFINE_EVAL_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
3624
|
-
"name",
|
|
3625
|
-
"target",
|
|
3626
|
-
"graders",
|
|
3627
|
-
"cases",
|
|
3628
|
-
"virtual"
|
|
3629
|
-
]);
|
|
3630
|
-
var DEFINE_EVAL_CASE_KEYS = /* @__PURE__ */ new Set(["name", "input", "expected", "expect"]);
|
|
3631
|
-
function isPlainObject2(value) {
|
|
3632
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3633
|
-
}
|
|
3634
|
-
function normalizeTarget(target) {
|
|
3635
|
-
if (!isPlainObject2(target)) {
|
|
3636
|
-
throw new Error('defineEval requires a "target" object: { flow: name } or { agent: name }');
|
|
3637
|
-
}
|
|
3638
|
-
const hasFlow = typeof target.flow === "string" && target.flow.length > 0;
|
|
3639
|
-
const hasAgent = typeof target.agent === "string" && target.agent.length > 0;
|
|
3640
|
-
if (hasFlow === hasAgent) {
|
|
3641
|
-
throw new Error(
|
|
3642
|
-
'defineEval "target" must name exactly one of flow or agent: { flow: "name" } XOR { agent: "name" }'
|
|
3643
|
-
);
|
|
3644
|
-
}
|
|
3645
|
-
const extraKeys = Object.keys(target).filter((k) => k !== "flow" && k !== "agent");
|
|
3646
|
-
if (extraKeys.length > 0) {
|
|
3647
|
-
throw new Error(`defineEval "target" has unknown field(s): ${extraKeys.join(", ")}`);
|
|
3648
|
-
}
|
|
3649
|
-
return hasFlow ? { flow: target.flow } : { agent: target.agent };
|
|
3650
|
-
}
|
|
3651
|
-
function validateGrader(grader, where) {
|
|
3652
|
-
if (!isPlainObject2(grader) || typeof grader.kind !== "string") {
|
|
3653
|
-
throw new Error(`defineEval: ${where} must be a grader object with a string "kind"`);
|
|
3654
|
-
}
|
|
3655
|
-
if (grader.kind === "ai") {
|
|
3656
|
-
if (typeof grader.criteria !== "string" || grader.criteria.trim().length === 0) {
|
|
3657
|
-
throw new Error(`defineEval: ${where} is an AI grader and requires non-empty "criteria"`);
|
|
3658
|
-
}
|
|
3659
|
-
return grader;
|
|
3660
|
-
}
|
|
3661
|
-
if (!CHECK_GRADER_KINDS.has(grader.kind)) {
|
|
3662
|
-
throw new Error(
|
|
3663
|
-
`defineEval: ${where} has unknown grader kind "${grader.kind}". Known kinds: ${[...CHECK_GRADER_KINDS].join(", ")}, ai.`
|
|
3664
|
-
);
|
|
3665
|
-
}
|
|
3666
|
-
return grader;
|
|
3667
|
-
}
|
|
3668
|
-
function normalizeCaseInput(input, where) {
|
|
3669
|
-
if (input === void 0) return {};
|
|
3670
|
-
if (!isPlainObject2(input)) {
|
|
3671
|
-
throw new Error(`defineEval: ${where} "input" must be an object`);
|
|
3672
|
-
}
|
|
3673
|
-
const out = {};
|
|
3674
|
-
if (input.variables !== void 0) {
|
|
3675
|
-
if (!isPlainObject2(input.variables)) {
|
|
3676
|
-
throw new Error(`defineEval: ${where} "input.variables" must be an object`);
|
|
3677
|
-
}
|
|
3678
|
-
out.variables = input.variables;
|
|
3679
|
-
}
|
|
3680
|
-
if (input.messages !== void 0) {
|
|
3681
|
-
if (!Array.isArray(input.messages)) {
|
|
3682
|
-
throw new Error(`defineEval: ${where} "input.messages" must be an array`);
|
|
3683
|
-
}
|
|
3684
|
-
out.messages = input.messages.map((m, i) => {
|
|
3685
|
-
if (!isPlainObject2(m) || typeof m.role !== "string" || typeof m.content !== "string") {
|
|
3686
|
-
throw new Error(`defineEval: ${where} "input.messages[${i}]" must be { role, content }`);
|
|
3764
|
+
if (!is422) {
|
|
3765
|
+
throw err;
|
|
3687
3766
|
}
|
|
3688
|
-
|
|
3689
|
-
|
|
3767
|
+
}
|
|
3768
|
+
const fullConfig = {
|
|
3769
|
+
...config,
|
|
3770
|
+
flow: { ...config.flow, contentHash }
|
|
3771
|
+
};
|
|
3772
|
+
return client.dispatch(fullConfig);
|
|
3690
3773
|
}
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
function defineEval(input) {
|
|
3694
|
-
if (!input || typeof input !== "object") {
|
|
3695
|
-
throw new Error("defineEval requires a definition object");
|
|
3774
|
+
async computeContentHash() {
|
|
3775
|
+
return computeFlowContentHash(this.steps);
|
|
3696
3776
|
}
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
);
|
|
3777
|
+
addRawStep(type, config) {
|
|
3778
|
+
const { name, enabled, when, ...stepConfig } = config;
|
|
3779
|
+
this.addStep(type, name, stepConfig, enabled, when);
|
|
3780
|
+
return this;
|
|
3702
3781
|
}
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3782
|
+
addStep(type, name, config, enabled = true, when) {
|
|
3783
|
+
this.stepCounter++;
|
|
3784
|
+
const cleanConfig = {};
|
|
3785
|
+
for (const [key, value] of Object.entries(config)) {
|
|
3786
|
+
if (value !== void 0) {
|
|
3787
|
+
cleanConfig[key] = value;
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
this.steps.push({
|
|
3791
|
+
id: `step-${this.stepCounter}`,
|
|
3792
|
+
type,
|
|
3793
|
+
name,
|
|
3794
|
+
order: this.stepCounter,
|
|
3795
|
+
enabled,
|
|
3796
|
+
...when ? { when } : {},
|
|
3797
|
+
config: cleanConfig
|
|
3798
|
+
});
|
|
3706
3799
|
}
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3800
|
+
};
|
|
3801
|
+
|
|
3802
|
+
// src/batches-namespace.ts
|
|
3803
|
+
var BatchesNamespace = class {
|
|
3804
|
+
constructor(getClient) {
|
|
3805
|
+
this.getClient = getClient;
|
|
3711
3806
|
}
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3807
|
+
/**
|
|
3808
|
+
* Schedule a batch operation
|
|
3809
|
+
*
|
|
3810
|
+
* Creates and schedules a batch to run a flow on all records of a type.
|
|
3811
|
+
* By default, runs immediately. Use `at` to schedule for a specific time.
|
|
3812
|
+
*
|
|
3813
|
+
* @example
|
|
3814
|
+
* ```typescript
|
|
3815
|
+
* // Run immediately
|
|
3816
|
+
* const batch = await Runtype.batches.schedule({
|
|
3817
|
+
* flowId: 'flow_123',
|
|
3818
|
+
* recordType: 'customers',
|
|
3819
|
+
* })
|
|
3820
|
+
*
|
|
3821
|
+
* // Schedule for later
|
|
3822
|
+
* const batch = await Runtype.batches.schedule({
|
|
3823
|
+
* flowId: 'flow_123',
|
|
3824
|
+
* recordType: 'customers',
|
|
3825
|
+
* at: new Date('2024-01-15T09:00:00Z'),
|
|
3826
|
+
* })
|
|
3827
|
+
*
|
|
3828
|
+
* // With options
|
|
3829
|
+
* const batch = await Runtype.batches.schedule({
|
|
3830
|
+
* flowId: 'flow_123',
|
|
3831
|
+
* recordType: 'customers',
|
|
3832
|
+
* concurrency: 5,
|
|
3833
|
+
* continueOnError: true,
|
|
3834
|
+
* filter: { status: 'active' },
|
|
3835
|
+
* limit: 100,
|
|
3836
|
+
* })
|
|
3837
|
+
* ```
|
|
3838
|
+
*/
|
|
3839
|
+
async schedule(config) {
|
|
3840
|
+
const client = this.getClient();
|
|
3841
|
+
const payload = {
|
|
3842
|
+
flowId: config.flowId,
|
|
3843
|
+
recordType: config.recordType
|
|
3844
|
+
};
|
|
3845
|
+
if (config.at) {
|
|
3846
|
+
payload.scheduledAt = config.at.toISOString();
|
|
3731
3847
|
}
|
|
3732
|
-
const
|
|
3733
|
-
|
|
3734
|
-
);
|
|
3735
|
-
|
|
3736
|
-
if (
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3848
|
+
const options = {};
|
|
3849
|
+
if (config.async !== void 0) options.async = config.async;
|
|
3850
|
+
if (config.concurrency !== void 0) options.concurrency = config.concurrency;
|
|
3851
|
+
if (config.continueOnError !== void 0) options.continueOnError = config.continueOnError;
|
|
3852
|
+
if (config.storeResults !== void 0) options.storeResults = config.storeResults;
|
|
3853
|
+
if (config.modelOverride !== void 0) options.modelOverride = config.modelOverride;
|
|
3854
|
+
if (Object.keys(options).length > 0) {
|
|
3855
|
+
payload.options = options;
|
|
3740
3856
|
}
|
|
3741
|
-
if (
|
|
3742
|
-
|
|
3857
|
+
if (config.filter) {
|
|
3858
|
+
payload.filter = config.filter;
|
|
3743
3859
|
}
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
input: normalizeCaseInput(c.input, `cases[${index}] ("${c.name}")`),
|
|
3747
|
-
...c.expected !== void 0 ? { expected: c.expected } : {},
|
|
3748
|
-
expect
|
|
3749
|
-
};
|
|
3750
|
-
});
|
|
3751
|
-
return { name, target, cases, virtual: input.virtual === true };
|
|
3752
|
-
}
|
|
3753
|
-
function normalizeForHash(value) {
|
|
3754
|
-
if (Array.isArray(value)) return value.map(normalizeForHash);
|
|
3755
|
-
if (isPlainObject2(value)) {
|
|
3756
|
-
const out = {};
|
|
3757
|
-
for (const key of Object.keys(value).sort()) {
|
|
3758
|
-
const v = value[key];
|
|
3759
|
-
if (v === void 0) continue;
|
|
3760
|
-
out[key] = normalizeForHash(v);
|
|
3860
|
+
if (config.limit !== void 0) {
|
|
3861
|
+
payload.limit = config.limit;
|
|
3761
3862
|
}
|
|
3762
|
-
return
|
|
3763
|
-
}
|
|
3764
|
-
return value;
|
|
3765
|
-
}
|
|
3766
|
-
async function computeEvalContentHash(definition) {
|
|
3767
|
-
const canonical = {
|
|
3768
|
-
target: normalizeForHash(definition.target),
|
|
3769
|
-
virtual: definition.virtual,
|
|
3770
|
-
cases: [...definition.cases].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0).map((c) => ({
|
|
3771
|
-
name: c.name,
|
|
3772
|
-
input: normalizeForHash(c.input),
|
|
3773
|
-
...c.expected !== void 0 ? { expected: normalizeForHash(c.expected) } : {},
|
|
3774
|
-
// Grader order preserved on purpose (it maps to the result index).
|
|
3775
|
-
expect: c.expect.map((g) => normalizeForHash(g))
|
|
3776
|
-
}))
|
|
3777
|
-
};
|
|
3778
|
-
const serialized = JSON.stringify(canonical);
|
|
3779
|
-
const encoded = new TextEncoder().encode(serialized);
|
|
3780
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
|
|
3781
|
-
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3782
|
-
}
|
|
3783
|
-
var serverHashMemo2 = /* @__PURE__ */ new WeakMap();
|
|
3784
|
-
function memoFor2(client) {
|
|
3785
|
-
let memo = serverHashMemo2.get(client);
|
|
3786
|
-
if (!memo) {
|
|
3787
|
-
memo = /* @__PURE__ */ new Map();
|
|
3788
|
-
serverHashMemo2.set(client, memo);
|
|
3863
|
+
return client.post("/batches", payload);
|
|
3789
3864
|
}
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3865
|
+
/**
|
|
3866
|
+
* Get batch status by ID
|
|
3867
|
+
*
|
|
3868
|
+
* @example
|
|
3869
|
+
* ```typescript
|
|
3870
|
+
* const status = await Runtype.batches.get('batch_456')
|
|
3871
|
+
* console.log(status.status, status.processedRecords, '/', status.totalRecords)
|
|
3872
|
+
* ```
|
|
3873
|
+
*/
|
|
3874
|
+
async get(batchId) {
|
|
3875
|
+
const client = this.getClient();
|
|
3876
|
+
return client.get(`/batches/${batchId}`);
|
|
3797
3877
|
}
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3878
|
+
/**
|
|
3879
|
+
* Cancel a batch operation
|
|
3880
|
+
*
|
|
3881
|
+
* Cancels a queued or running batch. Records already processed are not rolled back.
|
|
3882
|
+
*
|
|
3883
|
+
* @example
|
|
3884
|
+
* ```typescript
|
|
3885
|
+
* await Runtype.batches.cancel('batch_456')
|
|
3886
|
+
* ```
|
|
3887
|
+
*/
|
|
3888
|
+
async cancel(batchId) {
|
|
3889
|
+
const client = this.getClient();
|
|
3890
|
+
return client.post(`/batches/${batchId}/cancel`);
|
|
3809
3891
|
}
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3892
|
+
/**
|
|
3893
|
+
* List batch operations
|
|
3894
|
+
*
|
|
3895
|
+
* @example
|
|
3896
|
+
* ```typescript
|
|
3897
|
+
* // List all batches
|
|
3898
|
+
* const batches = await Runtype.batches.list()
|
|
3899
|
+
*
|
|
3900
|
+
* // Filter by status
|
|
3901
|
+
* const running = await Runtype.batches.list({ status: 'running' })
|
|
3902
|
+
*
|
|
3903
|
+
* // Filter by flow
|
|
3904
|
+
* const flowBatches = await Runtype.batches.list({ flowId: 'flow_123' })
|
|
3905
|
+
* ```
|
|
3906
|
+
*/
|
|
3907
|
+
async list(params) {
|
|
3908
|
+
const client = this.getClient();
|
|
3909
|
+
return client.get("/batches", params);
|
|
3816
3910
|
}
|
|
3817
|
-
|
|
3818
|
-
return converged;
|
|
3819
|
-
}
|
|
3820
|
-
async function pullEval(client, name) {
|
|
3821
|
-
return client.get("/eval/pull", { name });
|
|
3822
|
-
}
|
|
3823
|
-
async function runEvalSuite(client, input) {
|
|
3824
|
-
return client.post("/eval/run", input);
|
|
3825
|
-
}
|
|
3911
|
+
};
|
|
3826
3912
|
|
|
3827
3913
|
// src/evals-namespace.ts
|
|
3828
3914
|
var EvalRunner = class {
|
|
@@ -6102,7 +6188,7 @@ var Runtype = class {
|
|
|
6102
6188
|
|
|
6103
6189
|
// src/version.ts
|
|
6104
6190
|
var FALLBACK_VERSION = "0.0.0";
|
|
6105
|
-
var SDK_VERSION = "5.
|
|
6191
|
+
var SDK_VERSION = "5.6.0".length > 0 ? "5.6.0" : FALLBACK_VERSION;
|
|
6106
6192
|
var RUNTYPE_CLIENT_KIND = "sdk";
|
|
6107
6193
|
var SDK_USER_AGENT = `runtype-sdk/${SDK_VERSION} (typescript)`;
|
|
6108
6194
|
|