@runtypelabs/cli 2.59.1 → 2.59.4
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/chunk-0x7UKC1U.js +17 -0
- package/dist/chunk-2ySxXtcg.js +418 -0
- package/dist/chunk-3An_BRHv.js +501 -0
- package/dist/chunk-6uTxgdiY.js +16218 -0
- package/dist/chunk-87nRnNRG.js +1363 -0
- package/dist/chunk-B3wo8uRx.js +322 -0
- package/dist/chunk-BGp3ZdBy.js +560 -0
- package/dist/chunk-BJQv9Elp.js +107 -0
- package/dist/chunk-BOpO6xE-.js +6053 -0
- package/dist/chunk-BQXXCL8B.js +102 -0
- package/dist/chunk-BV-8isOd.js +34 -0
- package/dist/chunk-BVMhjGVe.js +826 -0
- package/dist/chunk-BW2fg46T.js +141 -0
- package/dist/chunk-BWJdY5Ag.js +36 -0
- package/dist/chunk-BYJOrLhh.js +302 -0
- package/dist/chunk-BYab8EVL.js +127 -0
- package/dist/chunk-BfEKvp7h.js +82 -0
- package/dist/chunk-BhgLSfH6.js +1123 -0
- package/dist/chunk-BirUxTtH.js +296 -0
- package/dist/chunk-BmB6UVZ9.js +1559 -0
- package/dist/chunk-BnWhIqW1.js +109 -0
- package/dist/chunk-Bpfnfk0t.js +123 -0
- package/dist/chunk-BrhgdnM3.js +108 -0
- package/dist/chunk-Bt0XAgX5.js +327 -0
- package/dist/chunk-Bu0FJr6S.js +84 -0
- package/dist/chunk-BvEoKpN3.js +255 -0
- package/dist/chunk-BxNJgYbE.js +28 -0
- package/dist/chunk-C1OFhd7F.js +6131 -0
- package/dist/chunk-C7e7WwbU.js +331 -0
- package/dist/chunk-CDTYP6bN.js +558 -0
- package/dist/chunk-CGDQoqDC.js +24 -0
- package/dist/chunk-CJraEBVd.js +127 -0
- package/dist/chunk-CQrNhOtv.js +40 -0
- package/dist/chunk-CW6AzBH6.js +128 -0
- package/dist/chunk-CXgL4w-9.js +317 -0
- package/dist/chunk-CZrqoPyf.js +233 -0
- package/dist/chunk-CcFCyqbB.js +83 -0
- package/dist/chunk-CdaIvO-y.js +485 -0
- package/dist/chunk-CehSXtgK.js +624 -0
- package/dist/chunk-Ciqkop65.js +218 -0
- package/dist/chunk-CoAWdSiE.js +43 -0
- package/dist/chunk-Crb4y7t4.js +830 -0
- package/dist/chunk-CrlqIfmb.js +337 -0
- package/dist/chunk-Cs4pwCQ-.js +324 -0
- package/dist/chunk-CwM_9y01.js +519 -0
- package/dist/chunk-CyRbIJg8.js +440 -0
- package/dist/chunk-D0OHsyMy.js +212 -0
- package/dist/chunk-D3y2bURs.js +1347 -0
- package/dist/chunk-D4_mJk03.js +2338 -0
- package/dist/chunk-D6C8rned.js +578 -0
- package/dist/chunk-D9ZbUnks.js +11 -0
- package/dist/chunk-DEPmLshX.js +834 -0
- package/dist/chunk-DFj1UPB2.js +330 -0
- package/dist/chunk-DGdFdZVf.js +197 -0
- package/dist/chunk-DGv7X9S_.js +63 -0
- package/dist/chunk-DJQ47WYZ.js +664 -0
- package/dist/chunk-DJw3O61j.js +1974 -0
- package/dist/chunk-DK4FbWVK.js +100 -0
- package/dist/chunk-DPuofXMp.js +3907 -0
- package/dist/chunk-DSRYhUus.js +993 -0
- package/dist/chunk-DajZ9Eg4.js +299 -0
- package/dist/chunk-Df4dunvU.js +195 -0
- package/dist/chunk-DjNTPhfO.js +481 -0
- package/dist/chunk-Dn8eaJuu.js +1042 -0
- package/dist/chunk-DwDWqSxM.js +637 -0
- package/dist/chunk-Dz7cEtxl.js +84 -0
- package/dist/chunk-E_0bgjrS.js +359 -0
- package/dist/chunk-KPYN2vUp.js +776 -0
- package/dist/chunk-KcqPQJBi.js +86 -0
- package/dist/chunk-N9H0JnoG.js +349 -0
- package/dist/chunk-NONFcXkn.js +728 -0
- package/dist/chunk-PrFjcpmH.js +256 -0
- package/dist/chunk-QSfNYiw9.js +11 -0
- package/dist/chunk-R265bzj9.js +226 -0
- package/dist/chunk-X9V_r6jM.js +166 -0
- package/dist/chunk-ZBtPUtyA.js +55 -0
- package/dist/chunk-ZCTfmOs7.js +4939 -0
- package/dist/chunk-h6oavj0t.js +97 -0
- package/dist/chunk-i7DwkvvD.js +42 -0
- package/dist/chunk-mG-4eZ6S.js +203 -0
- package/dist/chunk-o0ygUbCf.js +313 -0
- package/dist/chunk-ucu5lrDZ.js +5715 -0
- package/dist/index.js +11367 -73789
- package/package.json +5 -5
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { C as unknown, S as union, b as string, c as boolean, d as lazy, f as literal, h as object, i as _enum, l as custom, m as number, o as any, p as never, s as array, v as record, y as strictObject } from "./chunk-BOpO6xE-.js";
|
|
3
|
+
import { t as isRecord } from "./chunk-QSfNYiw9.js";
|
|
4
|
+
/**
|
|
5
|
+
* Well-known system variables always available at execution time, populated by the engine (system-vars.ts).
|
|
6
|
+
* Canonical set; flow-validator-core.ts and other consumers import it rather than re-declaring it.
|
|
7
|
+
* MESSAGE_VARIABLES are a subset, spread in below so there is a single source.
|
|
8
|
+
*/
|
|
9
|
+
const SYSTEM_VARIABLES = /* @__PURE__ */ new Set([
|
|
10
|
+
"_flow",
|
|
11
|
+
"_user",
|
|
12
|
+
"_execution",
|
|
13
|
+
"_record",
|
|
14
|
+
"_now",
|
|
15
|
+
"_schedule",
|
|
16
|
+
"_conversation",
|
|
17
|
+
"_tools",
|
|
18
|
+
"_endUser",
|
|
19
|
+
"_tenant",
|
|
20
|
+
.../* @__PURE__ */ new Set([
|
|
21
|
+
"messages",
|
|
22
|
+
"userMessage",
|
|
23
|
+
"lastMessage",
|
|
24
|
+
"messageCount"
|
|
25
|
+
])
|
|
26
|
+
]);
|
|
27
|
+
/**
|
|
28
|
+
* Canonical {{secret:KEY}} detection regex; KEY is upper-snake, e.g. {{secret:OPENAI_API_KEY}}.
|
|
29
|
+
* Narrower than the general {{var}} form so secret tokens never collide with parameter templates.
|
|
30
|
+
* Single source of truth; historical duplicates import this constant instead of re-declaring the literal.
|
|
31
|
+
* Build a fresh regex per call when iterating matches so the shared instance's lastIndex is never mutated.
|
|
32
|
+
*/
|
|
33
|
+
const SECRET_REF_PATTERN = /\{\{secret:([A-Z][A-Z0-9_]*[A-Z0-9])\}\}/g;
|
|
34
|
+
/**
|
|
35
|
+
* The recognized namespace prefixes and their semantics. Keyed by the literal
|
|
36
|
+
* prefix (with trailing colon). Adding a namespace here is the one-line change
|
|
37
|
+
* that teaches every layer about it.
|
|
38
|
+
*/
|
|
39
|
+
const RUNTIME_PREFIXES = {
|
|
40
|
+
"secret:": {
|
|
41
|
+
prefix: "secret:",
|
|
42
|
+
namespace: "secret",
|
|
43
|
+
resolvedBy: "the secret pre-pass (substituteSecretReferences) before the template engine runs — never the template engine itself",
|
|
44
|
+
mayReachModelUnresolved: false
|
|
45
|
+
},
|
|
46
|
+
"flow:": {
|
|
47
|
+
prefix: "flow:",
|
|
48
|
+
namespace: "flow",
|
|
49
|
+
resolvedBy: "the template engine, which treats it as an alias for the bare flow variable of the same name",
|
|
50
|
+
mayReachModelUnresolved: false
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Matches a leading `prefix:` segment. A prefix is a single identifier-ish
|
|
55
|
+
* segment followed by a colon at the very start of the token. Nested paths
|
|
56
|
+
* (`_record.metadata.field`) and fallback chains are NOT prefixes — they
|
|
57
|
+
* contain no leading `:`-terminated segment.
|
|
58
|
+
*/
|
|
59
|
+
const PREFIX_PATTERN = /^([A-Za-z][A-Za-z0-9_-]*):(.*)$/s;
|
|
60
|
+
/**
|
|
61
|
+
* Classify a {{...}} token's variable key; the one function every layer uses to decide what a token is.
|
|
62
|
+
* Pass the KEY with surrounding whitespace and any fallback operand already stripped by the caller.
|
|
63
|
+
* Examples: _now.iso -> system, topic -> plain, flow:topic -> flow, secret:OPENAI -> secret, flwo:topic -> unknown.
|
|
64
|
+
*/
|
|
65
|
+
function classifyVariableReference(token) {
|
|
66
|
+
const trimmed = token.trim();
|
|
67
|
+
const prefixMatch = PREFIX_PATTERN.exec(trimmed);
|
|
68
|
+
if (prefixMatch) {
|
|
69
|
+
const prefix = `${prefixMatch[1]}:`;
|
|
70
|
+
const rest = prefixMatch[2] ?? "";
|
|
71
|
+
const spec = RUNTIME_PREFIXES[prefix];
|
|
72
|
+
if (spec) return {
|
|
73
|
+
namespace: spec.namespace,
|
|
74
|
+
baseName: rest.trim()
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
namespace: "unknown",
|
|
78
|
+
raw: trimmed
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (isSystemVariable(trimmed)) return {
|
|
82
|
+
namespace: "system",
|
|
83
|
+
baseName: trimmed
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
namespace: "plain",
|
|
87
|
+
baseName: trimmed
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Whether a variable key is a system variable or a nested access on one
|
|
92
|
+
* (e.g. `_record.metadata.field`, `_flow.name`). The root segment is matched
|
|
93
|
+
* against {@link SYSTEM_VARIABLES}.
|
|
94
|
+
*/
|
|
95
|
+
function isSystemVariable(varName) {
|
|
96
|
+
const rootSegment = varName.split(".")[0] || "";
|
|
97
|
+
return SYSTEM_VARIABLES.has(rootSegment);
|
|
98
|
+
}
|
|
99
|
+
/** Error text for a caller-supplied input key in the reserved `_` system namespace. */
|
|
100
|
+
const RESERVED_INPUT_KEY_MESSAGE = "Input keys cannot start with '_' (reserved for system variables)";
|
|
101
|
+
/** Whether caller-supplied `inputs` stay out of the reserved `_` system namespace. */
|
|
102
|
+
function hasNoReservedInputKeys(inputs) {
|
|
103
|
+
if (!inputs) return true;
|
|
104
|
+
return Object.keys(inputs).every((key) => !key.startsWith("_"));
|
|
105
|
+
}
|
|
106
|
+
// ../shared/dist/chunk-9uTZDlBG.mjs
|
|
107
|
+
const nonNegativeInteger = number().int().nonnegative();
|
|
108
|
+
const annotations = {
|
|
109
|
+
title: string().optional(),
|
|
110
|
+
description: string().optional(),
|
|
111
|
+
default: unknown().optional()
|
|
112
|
+
};
|
|
113
|
+
const schemaNode = lazy(() => union([boolean(), schemaObject]));
|
|
114
|
+
const schemaObject = lazy(() => strictObject({
|
|
115
|
+
...annotations,
|
|
116
|
+
type: _enum([
|
|
117
|
+
"object",
|
|
118
|
+
"array",
|
|
119
|
+
"string",
|
|
120
|
+
"number",
|
|
121
|
+
"integer",
|
|
122
|
+
"boolean",
|
|
123
|
+
"null"
|
|
124
|
+
]).optional(),
|
|
125
|
+
properties: record(string(), schemaNode).optional(),
|
|
126
|
+
required: array(string()).optional(),
|
|
127
|
+
additionalProperties: schemaNode.optional(),
|
|
128
|
+
items: schemaNode.optional(),
|
|
129
|
+
prefixItems: array(schemaNode).optional(),
|
|
130
|
+
minItems: nonNegativeInteger.optional(),
|
|
131
|
+
maxItems: nonNegativeInteger.optional(),
|
|
132
|
+
uniqueItems: boolean().optional(),
|
|
133
|
+
minProperties: nonNegativeInteger.optional(),
|
|
134
|
+
maxProperties: nonNegativeInteger.optional(),
|
|
135
|
+
minLength: nonNegativeInteger.optional(),
|
|
136
|
+
maxLength: nonNegativeInteger.optional(),
|
|
137
|
+
minimum: number().optional(),
|
|
138
|
+
maximum: number().optional(),
|
|
139
|
+
exclusiveMinimum: number().optional(),
|
|
140
|
+
exclusiveMaximum: number().optional(),
|
|
141
|
+
multipleOf: number().positive().optional(),
|
|
142
|
+
enum: array(unknown()).min(1).optional(),
|
|
143
|
+
const: unknown().optional(),
|
|
144
|
+
format: _enum([
|
|
145
|
+
"date",
|
|
146
|
+
"date-time",
|
|
147
|
+
"time",
|
|
148
|
+
"email",
|
|
149
|
+
"uri",
|
|
150
|
+
"uuid"
|
|
151
|
+
]).optional(),
|
|
152
|
+
allOf: array(schemaObject).min(1).optional(),
|
|
153
|
+
anyOf: array(schemaObject).min(1).optional(),
|
|
154
|
+
oneOf: array(schemaObject).min(1).optional(),
|
|
155
|
+
not: schemaObject.optional()
|
|
156
|
+
}));
|
|
157
|
+
function boundedJson(value) {
|
|
158
|
+
const pending = [{
|
|
159
|
+
value,
|
|
160
|
+
depth: 0
|
|
161
|
+
}];
|
|
162
|
+
let count = 0;
|
|
163
|
+
while (pending.length) {
|
|
164
|
+
const current = pending.pop();
|
|
165
|
+
if (++count > 4096 || current.depth > 16) return false;
|
|
166
|
+
if (current.value && typeof current.value === "object") for (const child of Object.values(current.value)) pending.push({
|
|
167
|
+
value: child,
|
|
168
|
+
depth: current.depth + 1
|
|
169
|
+
});
|
|
170
|
+
else if (typeof current.value === "string" && current.value.length > 65536) return false;
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
/** Whether a persisted form schema can be enforced by the elicitation answer validator. */
|
|
175
|
+
function isSupportedElicitationSchema(schema) {
|
|
176
|
+
if (!boundedJson(schema)) return false;
|
|
177
|
+
const parsed = schemaNode.safeParse(schema);
|
|
178
|
+
return parsed.success && typeof parsed.data !== "boolean" && parsed.data.type === "object";
|
|
179
|
+
}
|
|
180
|
+
// ../shared/dist/chunk-CXNdCxH5.mjs
|
|
181
|
+
/** The Tenancy Assurance Levels, in the order `tenancyAssuranceLevelSchema` is built from. */
|
|
182
|
+
const TENANCY_ASSURANCE_LEVELS = [
|
|
183
|
+
"anonymous",
|
|
184
|
+
"asserted",
|
|
185
|
+
"verified"
|
|
186
|
+
];
|
|
187
|
+
const HOSTNAME_LABEL = /^(?!-)[a-z0-9-]{1,63}(?<!-)$/;
|
|
188
|
+
const IPV4_LITERAL = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
189
|
+
function isAudienceHostname(host) {
|
|
190
|
+
if (host.length === 0 || host.length > 253) return false;
|
|
191
|
+
const ipv4 = IPV4_LITERAL.exec(host);
|
|
192
|
+
if (ipv4) return ipv4.slice(1).every((octet) => Number(octet) <= 255);
|
|
193
|
+
return host.split(".").every((label) => HOSTNAME_LABEL.test(label));
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Normalize one `identityAudiences` entry: a lowercase hostname, or `*.` plus a hostname of at least
|
|
197
|
+
* two labels. Returns `null` for anything else (schemes, ports, paths, a bare `*`, `*.com`).
|
|
198
|
+
*/
|
|
199
|
+
function normalizeIdentityAudiencePattern(pattern) {
|
|
200
|
+
if (typeof pattern !== "string") return null;
|
|
201
|
+
const lowered = pattern.trim().toLowerCase();
|
|
202
|
+
if (lowered.startsWith("*.")) {
|
|
203
|
+
const suffix = lowered.slice(2);
|
|
204
|
+
if (IPV4_LITERAL.test(suffix) || suffix.split(".").length < 2) return null;
|
|
205
|
+
return isAudienceHostname(suffix) ? `*.${suffix}` : null;
|
|
206
|
+
}
|
|
207
|
+
return isAudienceHostname(lowered) ? lowered : null;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Whether an assertion audience is one the tool's `identityAudiences` permits. A `*.` pattern
|
|
211
|
+
* matches exactly one extra label, so `*.example.com` admits `pr-1.example.com` only.
|
|
212
|
+
*/
|
|
213
|
+
function identityAudienceAllowed(audience, patterns) {
|
|
214
|
+
const host = audience.toLowerCase();
|
|
215
|
+
return patterns.some((pattern) => {
|
|
216
|
+
if (!pattern.startsWith("*.")) return host === pattern;
|
|
217
|
+
const suffix = pattern.slice(1);
|
|
218
|
+
if (!host.endsWith(suffix)) return false;
|
|
219
|
+
const label = host.slice(0, -suffix.length);
|
|
220
|
+
return HOSTNAME_LABEL.test(label);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
// ../shared/dist/chunk-D0Rc89Pf.mjs
|
|
224
|
+
function extractSecretReferences(template) {
|
|
225
|
+
const keys = /* @__PURE__ */ new Set();
|
|
226
|
+
let match;
|
|
227
|
+
const regex = new RegExp(SECRET_REF_PATTERN.source, "g");
|
|
228
|
+
while ((match = regex.exec(template)) !== null) {
|
|
229
|
+
const key = match[1];
|
|
230
|
+
if (key) keys.add(key);
|
|
231
|
+
}
|
|
232
|
+
return Array.from(keys);
|
|
233
|
+
}
|
|
234
|
+
function extractSecretReferencesFromAnyValue(value, maxDepth = 10) {
|
|
235
|
+
const keys = /* @__PURE__ */ new Set();
|
|
236
|
+
walk(value, 0);
|
|
237
|
+
return Array.from(keys);
|
|
238
|
+
function walk(v, depth) {
|
|
239
|
+
if (depth > maxDepth) return;
|
|
240
|
+
if (typeof v === "string") {
|
|
241
|
+
for (const key of extractSecretReferences(v)) keys.add(key);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (Array.isArray(v)) {
|
|
245
|
+
for (const item of v) walk(item, depth + 1);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (v && typeof v === "object") for (const inner of Object.values(v)) walk(inner, depth + 1);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function isManagedDeploymentTargetPath(path) {
|
|
252
|
+
if (!path.startsWith("/") || path.startsWith("//") || /[\\?#\s{}]/.test(path)) return false;
|
|
253
|
+
let decoded = path;
|
|
254
|
+
for (let pass = 0; pass < 4; pass++) {
|
|
255
|
+
if (/[\\?#]/.test(decoded) || decoded.startsWith("//")) return false;
|
|
256
|
+
if (decoded.split("/").some((part) => part === "." || part === "..")) return false;
|
|
257
|
+
let next;
|
|
258
|
+
try {
|
|
259
|
+
next = decodeURIComponent(decoded);
|
|
260
|
+
} catch {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
if (next === decoded) return true;
|
|
264
|
+
if (next.split("/").length !== decoded.split("/").length) return false;
|
|
265
|
+
decoded = next;
|
|
266
|
+
}
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
const managedDeploymentTargetSchema = object({
|
|
270
|
+
kind: literal("sandbox-deployment"),
|
|
271
|
+
deploymentId: string().regex(/^sbd_[0-9abcdefghjkmnpqrstvwxyz]{20,32}$/),
|
|
272
|
+
path: string().refine(isManagedDeploymentTargetPath, "Expected a fixed relative application path").optional(),
|
|
273
|
+
bearerSecretName: string().refine((name) => extractSecretReferences(`{{secret:${name}}}`)[0] === name, "Expected a canonical managed secret name").optional()
|
|
274
|
+
}).strict();
|
|
275
|
+
const httpDestinationSchema = union([object({
|
|
276
|
+
url: string().min(1),
|
|
277
|
+
target: never().optional()
|
|
278
|
+
}), object({
|
|
279
|
+
target: managedDeploymentTargetSchema,
|
|
280
|
+
url: never().optional()
|
|
281
|
+
})]);
|
|
282
|
+
function validateManagedDeploymentTargetConfig(config) {
|
|
283
|
+
if (httpDestinationSchema.safeParse(config).success) return void 0;
|
|
284
|
+
if (config.target === void 0) return "URL is required";
|
|
285
|
+
const parsed = managedDeploymentTargetSchema.safeParse(config.target);
|
|
286
|
+
if (!parsed.success) return parsed.error.issues.map((issue) => issue.message).join("; ");
|
|
287
|
+
if (config.url !== void 0) return "Use either URL or a managed deployment target, not both";
|
|
288
|
+
}
|
|
289
|
+
/** Validate authored targets while preserving incomplete external-tool drafts and saved MCP references. */
|
|
290
|
+
function validateToolHttpDestination(tool, ctx) {
|
|
291
|
+
if (!tool || typeof tool !== "object" || Array.isArray(tool)) return;
|
|
292
|
+
if (tool.toolType !== "external" && tool.toolType !== "mcp") return;
|
|
293
|
+
if (!tool.config || typeof tool.config !== "object" || Array.isArray(tool.config)) return;
|
|
294
|
+
if (!("target" in tool.config)) return;
|
|
295
|
+
if (tool.config.target === void 0) return;
|
|
296
|
+
const issue = validateManagedDeploymentTargetConfig(tool.config);
|
|
297
|
+
if (issue) ctx.addIssue({
|
|
298
|
+
code: "custom",
|
|
299
|
+
path: ["config", "target"],
|
|
300
|
+
message: issue
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
const primitiveSchema = strictObject({
|
|
304
|
+
type: _enum([
|
|
305
|
+
"string",
|
|
306
|
+
"number",
|
|
307
|
+
"integer",
|
|
308
|
+
"boolean"
|
|
309
|
+
]),
|
|
310
|
+
title: string().optional(),
|
|
311
|
+
description: string().optional(),
|
|
312
|
+
enum: array(union([
|
|
313
|
+
string(),
|
|
314
|
+
number(),
|
|
315
|
+
boolean()
|
|
316
|
+
])).min(1).optional(),
|
|
317
|
+
minLength: number().int().nonnegative().optional(),
|
|
318
|
+
maxLength: number().int().nonnegative().optional(),
|
|
319
|
+
minimum: number().optional(),
|
|
320
|
+
maximum: number().optional(),
|
|
321
|
+
exclusiveMinimum: number().optional(),
|
|
322
|
+
exclusiveMaximum: number().optional()
|
|
323
|
+
}).superRefine((schema, ctx) => {
|
|
324
|
+
const stringBounds = schema.minLength !== void 0 || schema.maxLength !== void 0;
|
|
325
|
+
const numberBounds = [
|
|
326
|
+
schema.minimum,
|
|
327
|
+
schema.maximum,
|
|
328
|
+
schema.exclusiveMinimum,
|
|
329
|
+
schema.exclusiveMaximum
|
|
330
|
+
].some((bound) => bound !== void 0);
|
|
331
|
+
if (stringBounds && schema.type !== "string" || numberBounds && schema.type !== "number" && schema.type !== "integer") ctx.addIssue({
|
|
332
|
+
code: "custom",
|
|
333
|
+
message: "Bounds must match the property type"
|
|
334
|
+
});
|
|
335
|
+
if (schema.enum?.some((value) => schema.type === "integer" ? !Number.isInteger(value) : typeof value !== schema.type)) ctx.addIssue({
|
|
336
|
+
code: "custom",
|
|
337
|
+
message: "Enum values must match the property type"
|
|
338
|
+
});
|
|
339
|
+
if ((schema.minLength ?? 0) > (schema.maxLength ?? Infinity) || (schema.minimum ?? -Infinity) > (schema.maximum ?? Infinity)) ctx.addIssue({
|
|
340
|
+
code: "custom",
|
|
341
|
+
message: "Minimum must not exceed maximum"
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
const toolElicitationConfigSchema = strictObject({
|
|
345
|
+
message: string().min(1).max(4096),
|
|
346
|
+
requestedSchema: strictObject({
|
|
347
|
+
type: literal("object"),
|
|
348
|
+
title: string().optional(),
|
|
349
|
+
description: string().optional(),
|
|
350
|
+
properties: record(string(), primitiveSchema),
|
|
351
|
+
required: array(string()).optional(),
|
|
352
|
+
additionalProperties: literal(false)
|
|
353
|
+
}),
|
|
354
|
+
merge: literal("parameters")
|
|
355
|
+
});
|
|
356
|
+
function canonicalToolInputDefinition(value) {
|
|
357
|
+
if (Array.isArray(value)) return `[${value.map(canonicalToolInputDefinition).join(",")}]`;
|
|
358
|
+
if (isRecord(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalToolInputDefinition(value[key])}`).join(",")}}`;
|
|
359
|
+
return JSON.stringify(value) ?? "null";
|
|
360
|
+
}
|
|
361
|
+
/** Validate authored input against the final dispatch schema at every definition boundary. */
|
|
362
|
+
function validateToolElicitationDefinition(tool, ctx) {
|
|
363
|
+
if (!isRecord(tool)) {
|
|
364
|
+
ctx.addIssue({
|
|
365
|
+
code: "custom",
|
|
366
|
+
message: "Runtime tool definition must be an object"
|
|
367
|
+
});
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const config = isRecord(tool.config) ? tool.config : void 0;
|
|
371
|
+
if (config?.elicit === void 0) return;
|
|
372
|
+
const issue = (message) => {
|
|
373
|
+
ctx.addIssue({
|
|
374
|
+
code: "custom",
|
|
375
|
+
path: ["config", "elicit"],
|
|
376
|
+
message
|
|
377
|
+
});
|
|
378
|
+
};
|
|
379
|
+
if (tool.toolType !== "external" && tool.toolType !== "custom") {
|
|
380
|
+
issue("Authored input is supported only on external and custom tools");
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const parsed = toolElicitationConfigSchema.safeParse(config.elicit);
|
|
384
|
+
if (!parsed.success) {
|
|
385
|
+
for (const error of parsed.error.issues) ctx.addIssue({
|
|
386
|
+
...error,
|
|
387
|
+
path: [
|
|
388
|
+
"config",
|
|
389
|
+
"elicit",
|
|
390
|
+
...error.path
|
|
391
|
+
]
|
|
392
|
+
});
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const final = tool.parametersSchema;
|
|
396
|
+
const allowedRootKeys = /* @__PURE__ */ new Set([
|
|
397
|
+
"type",
|
|
398
|
+
"title",
|
|
399
|
+
"description",
|
|
400
|
+
"properties",
|
|
401
|
+
"required",
|
|
402
|
+
"additionalProperties"
|
|
403
|
+
]);
|
|
404
|
+
if (!isSupportedElicitationSchema(final) || !isRecord(final) || final.type !== "object" || final.additionalProperties !== false || !isRecord(final.properties) || Object.keys(final).some((key) => !allowedRootKeys.has(key))) {
|
|
405
|
+
issue("Final parametersSchema must be a closed, projectable object without cross-field constraints");
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const requested = parsed.data.requestedSchema;
|
|
409
|
+
const names = Object.keys(requested.properties);
|
|
410
|
+
const required = requested.required ?? [];
|
|
411
|
+
const finalRequired = Array.isArray(final.required) ? final.required : [];
|
|
412
|
+
if (!names.length || required.some((name) => !names.includes(name)) || new Set(required).size !== required.length || finalRequired.some((name) => typeof name !== "string" || !Object.prototype.hasOwnProperty.call(final.properties, name))) issue("Requested properties and required fields must be nonempty and well formed");
|
|
413
|
+
for (const name of names) {
|
|
414
|
+
if (name.startsWith("_") || [
|
|
415
|
+
"constructor",
|
|
416
|
+
"prototype",
|
|
417
|
+
"__proto__"
|
|
418
|
+
].includes(name) || tool.hiddenParameterNames?.includes(name)) issue(`Human-owned property is reserved or protected: ${name}`);
|
|
419
|
+
if (!Object.prototype.hasOwnProperty.call(final.properties, name) || canonicalToolInputDefinition(final.properties[name]) !== canonicalToolInputDefinition(requested.properties[name]) || finalRequired.includes(name) !== required.includes(name)) issue(`Human-owned property must match the final schema and requiredness: ${name}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const IDENTITY_AUDIENCE_TOOL_TYPES = /* @__PURE__ */ new Set(["external", "mcp"]);
|
|
423
|
+
const URL_AUTHORITY = /^\s*(https?):\/\/([^/?#]*)/i;
|
|
424
|
+
/** The hostname of a URL whose authority is authored literally; `undefined` when it is templated or unparseable. */
|
|
425
|
+
function literalUrlHost(url) {
|
|
426
|
+
if (typeof url !== "string") return void 0;
|
|
427
|
+
const match = URL_AUTHORITY.exec(url);
|
|
428
|
+
if (!match || match[2].includes("{{")) return void 0;
|
|
429
|
+
try {
|
|
430
|
+
return new URL(`${match[1].toLowerCase()}://${match[2]}`).hostname.toLowerCase() || void 0;
|
|
431
|
+
} catch {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* The problem with a tool's `config.identityAudiences`, the hosts its `{{_identity.token}}` may be minted
|
|
437
|
+
* for, or `undefined` when it is absent or valid. A literal URL host must itself be listed.
|
|
438
|
+
*/
|
|
439
|
+
function toolIdentityAudiencesIssue(tool) {
|
|
440
|
+
const config = isRecord(tool) && isRecord(tool.config) ? tool.config : void 0;
|
|
441
|
+
if (config?.identityAudiences === void 0) return void 0;
|
|
442
|
+
if (!IDENTITY_AUDIENCE_TOOL_TYPES.has(tool.toolType)) return "identityAudiences is supported only on external and mcp tools";
|
|
443
|
+
const raw = config.identityAudiences;
|
|
444
|
+
if (!Array.isArray(raw) || raw.length === 0 || raw.length > 20) return `identityAudiences must list 1 to 20 hostnames`;
|
|
445
|
+
const patterns = [];
|
|
446
|
+
for (const entry of raw) {
|
|
447
|
+
const normalized = normalizeIdentityAudiencePattern(entry);
|
|
448
|
+
if (!normalized) return `identityAudiences entry ${JSON.stringify(entry)} must be a hostname such as "api.example.com" or a one-label wildcard such as "*.staging.example.com"`;
|
|
449
|
+
patterns.push(normalized);
|
|
450
|
+
}
|
|
451
|
+
const host = literalUrlHost(config.url);
|
|
452
|
+
if (host && !identityAudienceAllowed(host, patterns)) return `The tool URL host "${host}" is not listed in identityAudiences`;
|
|
453
|
+
}
|
|
454
|
+
/** The same check for an inline MCP server, whose discovered tools inherit its `identityAudiences`. */
|
|
455
|
+
function mcpServerIdentityAudiencesIssue(server) {
|
|
456
|
+
if (server.identityAudiences === void 0) return void 0;
|
|
457
|
+
return toolIdentityAudiencesIssue({
|
|
458
|
+
toolType: "mcp",
|
|
459
|
+
config: {
|
|
460
|
+
url: server.url,
|
|
461
|
+
identityAudiences: server.identityAudiences
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
/** Zod refinement form of `toolIdentityAudiencesIssue`, wired at every tool definition boundary. */
|
|
466
|
+
function validateToolIdentityAudiences(tool, ctx) {
|
|
467
|
+
const message = toolIdentityAudiencesIssue(tool);
|
|
468
|
+
if (message) ctx.addIssue({
|
|
469
|
+
code: "custom",
|
|
470
|
+
path: ["config", "identityAudiences"],
|
|
471
|
+
message
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
const ToolExecutionStatus = {
|
|
475
|
+
PENDING: "pending",
|
|
476
|
+
SUCCESS: "success",
|
|
477
|
+
FAILED: "failed",
|
|
478
|
+
WAITING_FOR_LOCAL: "waiting_for_local",
|
|
479
|
+
/** The call paused at an approval gate and has not been decided. */
|
|
480
|
+
AWAITING_APPROVAL: "awaiting_approval",
|
|
481
|
+
/** The approver declined (or the approval timed out); the tool never ran. */
|
|
482
|
+
DENIED: "denied"
|
|
483
|
+
};
|
|
484
|
+
ToolExecutionStatus.PENDING, ToolExecutionStatus.WAITING_FOR_LOCAL, ToolExecutionStatus.AWAITING_APPROVAL;
|
|
485
|
+
const MCPServerCategory = {
|
|
486
|
+
COMMUNICATION: "communication",
|
|
487
|
+
PRODUCTIVITY: "productivity",
|
|
488
|
+
DEVELOPMENT: "development",
|
|
489
|
+
PROJECT_MANAGEMENT: "project_management"
|
|
490
|
+
};
|
|
491
|
+
const subagentExecutionModeSchema = _enum(["attached", "detached"]);
|
|
492
|
+
const subagentNotificationModeSchema = _enum([
|
|
493
|
+
"none",
|
|
494
|
+
"narrate",
|
|
495
|
+
"react"
|
|
496
|
+
]);
|
|
497
|
+
object({
|
|
498
|
+
/** Attached is the backwards-compatible default. */
|
|
499
|
+
mode: subagentExecutionModeSchema.optional(),
|
|
500
|
+
/** Detached tools default to `narrate`; attached tools ignore this field. */
|
|
501
|
+
notify: subagentNotificationModeSchema.optional(),
|
|
502
|
+
/** Absolute background-run wall-clock ceiling. */
|
|
503
|
+
maxBudgetMs: number().int().min(1e3).max(864e5).optional(),
|
|
504
|
+
/** Optional resetting silence budget after observable progress. */
|
|
505
|
+
noProgressBudgetMs: number().int().min(1e3).max(864e5).optional()
|
|
506
|
+
});
|
|
507
|
+
object({
|
|
508
|
+
kind: literal("subagent_run"),
|
|
509
|
+
runId: string(),
|
|
510
|
+
/**
|
|
511
|
+
* Which durable lane the child executes on. `'durable'` is the ADR 0020
|
|
512
|
+
* session DO; `'runtype'` is the in-process detached-run DO.
|
|
513
|
+
*/
|
|
514
|
+
agentType: _enum([
|
|
515
|
+
"runtype",
|
|
516
|
+
"claude_managed",
|
|
517
|
+
"durable"
|
|
518
|
+
]),
|
|
519
|
+
status: _enum(["queued", "running"]),
|
|
520
|
+
startedAt: string()
|
|
521
|
+
}).extend({
|
|
522
|
+
agentType: literal("durable"),
|
|
523
|
+
status: _enum(["paused", "detached"]),
|
|
524
|
+
awaitReason: string().optional(),
|
|
525
|
+
approvalId: string().optional(),
|
|
526
|
+
toolName: string().optional(),
|
|
527
|
+
/** How the child is resumed, and whether its result is delivered to the parent conversation. */
|
|
528
|
+
resumeHint: string()
|
|
529
|
+
});
|
|
530
|
+
/**
|
|
531
|
+
* Opt-in configuration for agent-driven dynamic subagent spawning (surface C).
|
|
532
|
+
* Setting subagentConfig synthesizes a spawn_subagent tool into the AI-SDK tool list.
|
|
533
|
+
* The parent's LLM picks the child's task, tool subset, and optionally systemPrompt/model,
|
|
534
|
+
* drawing only from toolPool.
|
|
535
|
+
*/
|
|
536
|
+
const agentSubagentConfigSchema = object({
|
|
537
|
+
/**
|
|
538
|
+
* Pool of tool IDs the parent is permitted to grant to ad-hoc subagents.
|
|
539
|
+
* Supports exact IDs, wildcards (`mcp:*`, `builtin:*`, `platform:*`), and
|
|
540
|
+
* runtime-tool names. Must be a SUBSET of the parent's resolved tools —
|
|
541
|
+
* validated at dispatch (no privilege escalation).
|
|
542
|
+
*/
|
|
543
|
+
toolPool: array(string()),
|
|
544
|
+
/** Accepted but not applied yet: a spawned child's turn cap comes only from the model's `maxTurns` request. */
|
|
545
|
+
defaultMaxTurns: number().optional(),
|
|
546
|
+
/** Hard cap a spawned subagent can request. Default 10. */
|
|
547
|
+
maxTurnsLimit: number().optional(),
|
|
548
|
+
/** Hard ceiling on total spawns per top-level parent invocation. Default 5. */
|
|
549
|
+
maxSpawnsPerRun: number().optional(),
|
|
550
|
+
/** Default model for spawned subagents. Defaults to parent's model. */
|
|
551
|
+
defaultModel: string().optional(),
|
|
552
|
+
/**
|
|
553
|
+
* Whether a spawned subagent can itself spawn subagents. Default `false`
|
|
554
|
+
* (Anthropic-aligned). Even when `true`, MAX_NESTED_DEPTH still bounds the
|
|
555
|
+
* recursion tree.
|
|
556
|
+
*/
|
|
557
|
+
allowNesting: boolean().optional(),
|
|
558
|
+
/** Default per-spawn timeout. Default 300_000 (5 min). */
|
|
559
|
+
defaultTimeoutMs: number().optional(),
|
|
560
|
+
/** Execution modes the model may request. Default `['attached']`. */
|
|
561
|
+
executionModes: array(subagentExecutionModeSchema).min(1).optional(),
|
|
562
|
+
/** Default mode when the model omits `executionMode`. */
|
|
563
|
+
defaultExecutionMode: subagentExecutionModeSchema.optional(),
|
|
564
|
+
/** Default completion policy for detached dynamic children. */
|
|
565
|
+
defaultNotify: subagentNotificationModeSchema.optional(),
|
|
566
|
+
/** Absolute wall-clock ceiling for detached dynamic children. */
|
|
567
|
+
detachedMaxBudgetMs: number().int().min(1e3).max(864e5).optional(),
|
|
568
|
+
/** Optional resetting silence budget for detached dynamic children. */
|
|
569
|
+
detachedNoProgressBudgetMs: number().int().min(1e3).max(864e5).optional()
|
|
570
|
+
});
|
|
571
|
+
/**
|
|
572
|
+
* Configuration for the synthesized `code_mode` agent tool.
|
|
573
|
+
* When set on AgentToolsConfig, the prompt-step layer creates a tool that
|
|
574
|
+
* lets the LLM write and execute JavaScript with access to a subset of tools.
|
|
575
|
+
*/
|
|
576
|
+
const codeModeConfigSchema = object({
|
|
577
|
+
/** Tool IDs available in the sandbox (subset of agent's tools, supports wildcards like `mcp:*`) */
|
|
578
|
+
toolPool: array(string()),
|
|
579
|
+
/** Optional custom description prefix for the synthesized tool */
|
|
580
|
+
description: string().optional(),
|
|
581
|
+
/** Sandbox execution timeout in ms (default 60000) */
|
|
582
|
+
timeoutMs: number().optional()
|
|
583
|
+
});
|
|
584
|
+
const toolLimitSchema = object({
|
|
585
|
+
maxCalls: number().optional(),
|
|
586
|
+
required: boolean().optional()
|
|
587
|
+
});
|
|
588
|
+
/**
|
|
589
|
+
* Approval configuration for human-in-the-loop tool approval.
|
|
590
|
+
* Lives under `tools.approval` in agent/flow configs.
|
|
591
|
+
*/
|
|
592
|
+
const approvalConfigSchema = object({
|
|
593
|
+
/** Tools that require user approval before execution.
|
|
594
|
+
* string[]: tool names/patterns ("send_email", "mcp:*", "builtin:*")
|
|
595
|
+
* true: all tools require approval
|
|
596
|
+
* false/undefined: no approval required (default) */
|
|
597
|
+
require: union([array(string()), boolean()]).optional(),
|
|
598
|
+
/** Timeout in ms for approval response (default: 300000 = 5 min) */
|
|
599
|
+
timeout: number().optional(),
|
|
600
|
+
/** Messaging channels only: whether a timed-out approval posts the agent's follow-up.
|
|
601
|
+
* Default true. With false the run ends on timeout without resuming the agent, so nothing is sent. */
|
|
602
|
+
notifyUser: boolean().optional(),
|
|
603
|
+
/** Whether to ask the model for a per-call justification on approval-gated
|
|
604
|
+
* tools by injecting a reserved optional `_approvalReason` string into the
|
|
605
|
+
* model-visible parameters schema. The value is stripped before the tool
|
|
606
|
+
* executes and surfaced to the approver. Default: true (opt out with false). */
|
|
607
|
+
requestReason: boolean().optional(),
|
|
608
|
+
/**
|
|
609
|
+
* Who may answer the gate. `owner` (default) is authorization by the builder org;
|
|
610
|
+
* `end-user` lets a client-token chat visitor give consent (confirm their own order);
|
|
611
|
+
* never for policy or budget gates. Stamped into the pause at pause time.
|
|
612
|
+
*/
|
|
613
|
+
approver: _enum(["owner", "end-user"]).optional(),
|
|
614
|
+
/**
|
|
615
|
+
* Persistent "always" choices at the human approval prompt; user-facing options, not auto-behavior.
|
|
616
|
+
* Keys mirror the grant decision enum (alwaysAllow -> 'allow', alwaysDeny -> 'deny').
|
|
617
|
+
* Default: both off; this is the sole control for persisted approval grants (no Flagship flag).
|
|
618
|
+
* See docs/features/planning/2026-06-13-cross-session-always-allow-approval-grants.md
|
|
619
|
+
*/
|
|
620
|
+
choices: object({
|
|
621
|
+
alwaysAllow: boolean().optional(),
|
|
622
|
+
alwaysDeny: boolean().optional()
|
|
623
|
+
}).optional()
|
|
624
|
+
});
|
|
625
|
+
const toolSearchConfigSchema = object({
|
|
626
|
+
/** On by default for agents; false opts out, true forces it on below the threshold. Multi-turn runs only. */
|
|
627
|
+
enabled: boolean().optional(),
|
|
628
|
+
/** Tool count at which search activates (default: 20) */
|
|
629
|
+
threshold: number().optional(),
|
|
630
|
+
/** Tool names or IDs that should always be in the hot set */
|
|
631
|
+
alwaysLoaded: array(string()).optional()
|
|
632
|
+
});
|
|
633
|
+
/**
|
|
634
|
+
* When a durable host may run a non-idempotent server-side tool. `mandatory`: only once its
|
|
635
|
+
* intent is committed; a refused intent answers `TOOL_INTENT_NOT_COMMITTED` and never runs it.
|
|
636
|
+
* `best-effort` (the default): the intent write never blocks the call.
|
|
637
|
+
*/
|
|
638
|
+
const toolIntentPolicySchema = _enum(["mandatory", "best-effort"]);
|
|
639
|
+
/**
|
|
640
|
+
* Unified tool configuration used by both agents and flow steps; the canonical source of truth.
|
|
641
|
+
* AgentToolsConfig is z.infer'd from this; fpo-schema.ts, runtime/schemas.ts, and skill-manifest-types.ts are documented projections.
|
|
642
|
+
* Distinct from the simpler ToolsConfig in client-types.ts (client SDK requests).
|
|
643
|
+
* runtimeTools/mcpServers use z.custom<T>() so structural validation stays deferred to validateRuntimeTools.
|
|
644
|
+
*/
|
|
645
|
+
const agentToolsConfigSchema = object({
|
|
646
|
+
/** IDs of tools to enable (builtin:*, mcp:*, platform:*, platform:orthogonal:*, custom tool IDs) */
|
|
647
|
+
toolIds: array(string()).optional(),
|
|
648
|
+
/** Per-tool parameter configurations, keyed by tool ID */
|
|
649
|
+
toolConfigs: record(string(), custom()).optional(),
|
|
650
|
+
/** Inline tool definitions (no DB storage needed) */
|
|
651
|
+
runtimeTools: array(custom().superRefine(validateToolHttpDestination).superRefine(validateToolElicitationDefinition).superRefine(validateToolIdentityAudiences)).optional(),
|
|
652
|
+
/** Runtime MCP server connections */
|
|
653
|
+
mcpServers: array(custom()).optional(),
|
|
654
|
+
/** Maximum tool calls per execution (renamed from maxTurns in agents) */
|
|
655
|
+
maxToolCalls: number().optional(),
|
|
656
|
+
/** Tool calling strategy */
|
|
657
|
+
toolCallStrategy: _enum([
|
|
658
|
+
"auto",
|
|
659
|
+
"required",
|
|
660
|
+
"none"
|
|
661
|
+
]).optional(),
|
|
662
|
+
/** Per-tool call limits */
|
|
663
|
+
perToolLimits: record(string(), toolLimitSchema).optional(),
|
|
664
|
+
/** Human-in-the-loop approval configuration */
|
|
665
|
+
approval: approvalConfigSchema.optional(),
|
|
666
|
+
/**
|
|
667
|
+
* Opt-in: enables agent-driven dynamic subagent spawning. When set, the
|
|
668
|
+
* prompt-step tool-prep layer synthesizes a `spawn_subagent` tool into the
|
|
669
|
+
* AI-SDK tool list with a dynamically-generated description listing the
|
|
670
|
+
* tools the parent's LLM can grant. See `AgentSubagentConfig` for fields.
|
|
671
|
+
*/
|
|
672
|
+
subagentConfig: agentSubagentConfigSchema.optional(),
|
|
673
|
+
/**
|
|
674
|
+
* Opt-in: enables a synthesized `code_mode` tool that lets agents execute
|
|
675
|
+
* JavaScript code in a sandboxed V8 isolate with access to a subset of
|
|
676
|
+
* the agent's tools. The LLM writes code that calls tool functions
|
|
677
|
+
* deterministically instead of making individual tool calls.
|
|
678
|
+
*/
|
|
679
|
+
codeModeConfig: codeModeConfigSchema.optional(),
|
|
680
|
+
/**
|
|
681
|
+
* Tool search configuration; when total tool count exceeds the threshold, deferred loading activates.
|
|
682
|
+
* Only a hot set of tools is sent to the model each turn; tool_search lets it discover more on demand.
|
|
683
|
+
* On by default for agents: activates on a multi-turn run once the tool count reaches 20, even when omitted.
|
|
684
|
+
* Set enabled: false to force all tools to be sent regardless of count.
|
|
685
|
+
*/
|
|
686
|
+
toolSearch: toolSearchConfigSchema.optional(),
|
|
687
|
+
/** Intent policy for non-idempotent server-side tools on a durable host; default `best-effort`. */
|
|
688
|
+
intent: toolIntentPolicySchema.optional()
|
|
689
|
+
});
|
|
690
|
+
const CreateToolSchema = object({
|
|
691
|
+
name: string().min(1).max(100),
|
|
692
|
+
description: string().min(1).max(500),
|
|
693
|
+
toolType: _enum([
|
|
694
|
+
"flow",
|
|
695
|
+
"custom",
|
|
696
|
+
"external",
|
|
697
|
+
"graphql",
|
|
698
|
+
"mcp",
|
|
699
|
+
"local",
|
|
700
|
+
"subagent"
|
|
701
|
+
]),
|
|
702
|
+
parametersSchema: record(string(), any()),
|
|
703
|
+
config: record(string(), any())
|
|
704
|
+
}).superRefine(validateToolElicitationDefinition).superRefine(validateToolIdentityAudiences);
|
|
705
|
+
object({
|
|
706
|
+
toolId: string().optional(),
|
|
707
|
+
parameters: record(string(), any()),
|
|
708
|
+
toolConfig: record(string(), unknown()).optional().describe("Built-in tools only: the instance config an agent's toolConfigs entry would carry, validated against the tool's configSchema. Rejected for a saved tool."),
|
|
709
|
+
context: object({
|
|
710
|
+
flowExecutionId: string().optional(),
|
|
711
|
+
stepId: string().optional(),
|
|
712
|
+
userId: string()
|
|
713
|
+
}).optional()
|
|
714
|
+
});
|
|
715
|
+
/**
|
|
716
|
+
* Parse a tool instance ID into its base ID and instance ID
|
|
717
|
+
* @param toolId - Tool ID that may include instance suffix (e.g., "builtin:vector-search#abc123")
|
|
718
|
+
* @returns Object with baseId (e.g., "builtin:vector-search") and instanceId (e.g., "abc123")
|
|
719
|
+
*/
|
|
720
|
+
function parseToolInstanceId(toolId) {
|
|
721
|
+
const hashIndex = toolId.indexOf("#");
|
|
722
|
+
if (hashIndex === -1) return {
|
|
723
|
+
baseId: toolId,
|
|
724
|
+
instanceId: ""
|
|
725
|
+
};
|
|
726
|
+
return {
|
|
727
|
+
baseId: toolId.substring(0, hashIndex),
|
|
728
|
+
instanceId: toolId.substring(hashIndex + 1)
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Check if a tool ID represents a built-in tool
|
|
733
|
+
*/
|
|
734
|
+
function isBuiltInToolId(toolId) {
|
|
735
|
+
return toolId.startsWith("builtin:");
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Check if a tool ID represents a platform integration tool
|
|
739
|
+
*/
|
|
740
|
+
function isPlatformToolId(toolId) {
|
|
741
|
+
return toolId.startsWith("platform:");
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Check if a tool ID represents a built-in integration tool (Slack, GitHub, etc.)
|
|
745
|
+
* Format: builtin:serverId:toolName (e.g., builtin:slack:send_message)
|
|
746
|
+
* This is a 3-part builtin ID, distinct from 2-part builtin IDs like builtin:gpt-image-2
|
|
747
|
+
* Note: Tool instance IDs like builtin:vector-search#abc123 are NOT integration tools
|
|
748
|
+
*/
|
|
749
|
+
const INTEGRATION_SERVER_IDS = /* @__PURE__ */ new Set([
|
|
750
|
+
"slack",
|
|
751
|
+
"google",
|
|
752
|
+
"linear",
|
|
753
|
+
"github",
|
|
754
|
+
"telegram",
|
|
755
|
+
"imessage",
|
|
756
|
+
"granola"
|
|
757
|
+
]);
|
|
758
|
+
function isBuiltInIntegrationToolId(toolId) {
|
|
759
|
+
if (!toolId.startsWith("builtin:")) return false;
|
|
760
|
+
const { baseId } = parseToolInstanceId(toolId);
|
|
761
|
+
const parts = baseId.split(":");
|
|
762
|
+
if (parts.length < 3) return false;
|
|
763
|
+
return INTEGRATION_SERVER_IDS.has(parts[1]);
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Check if a tool ID represents an MCP tool (custom MCP servers only)
|
|
767
|
+
* Format: mcp:serverId:toolName (e.g., mcp:custom_myserver:toolname)
|
|
768
|
+
*/
|
|
769
|
+
function isMCPToolId(toolId) {
|
|
770
|
+
return toolId.startsWith("mcp:");
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Check if a string is a name-based saved-tool reference (`tool:<name>`).
|
|
774
|
+
*/
|
|
775
|
+
function isNamedToolRef(toolId) {
|
|
776
|
+
return toolId.startsWith("tool:") && toolId.length > 5;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Parse a tool:<name> reference into its name, splitting on the first colon only.
|
|
780
|
+
* The remainder is the tool name verbatim since names may contain colons, spaces, any varchar content.
|
|
781
|
+
* Exact, case-sensitive; no trimming. Returns null when toolId isn't a valid named tool ref.
|
|
782
|
+
*/
|
|
783
|
+
function parseNamedToolRef(toolId) {
|
|
784
|
+
if (!isNamedToolRef(toolId)) return null;
|
|
785
|
+
return { name: toolId.slice(5) };
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Separate built-in, platform, integration, MCP, and custom tool IDs from a mixed array
|
|
789
|
+
* - builtInToolIds: 2-part IDs like builtin:gpt-image-2
|
|
790
|
+
* - integrationToolIds: 3-part IDs like builtin:slack:send_message
|
|
791
|
+
* - mcpToolIds: custom MCP server tools like mcp:custom_xxx:tool
|
|
792
|
+
*/
|
|
793
|
+
function separateToolIds(toolIds) {
|
|
794
|
+
const builtInToolIds = [];
|
|
795
|
+
const platformToolIds = [];
|
|
796
|
+
const integrationToolIds = [];
|
|
797
|
+
const mcpToolIds = [];
|
|
798
|
+
const dataConnectionToolIds = [];
|
|
799
|
+
const namedToolIds = [];
|
|
800
|
+
const customToolIds = [];
|
|
801
|
+
for (const toolId of toolIds) if (isDataConnectionToolId(toolId)) dataConnectionToolIds.push(toolId);
|
|
802
|
+
else if (isPlatformToolId(toolId)) platformToolIds.push(toolId);
|
|
803
|
+
else if (isBuiltInIntegrationToolId(toolId)) integrationToolIds.push(toolId);
|
|
804
|
+
else if (isBuiltInToolId(toolId)) builtInToolIds.push(toolId);
|
|
805
|
+
else if (isMCPToolId(toolId)) mcpToolIds.push(toolId);
|
|
806
|
+
else if (isNamedToolRef(toolId)) namedToolIds.push(toolId);
|
|
807
|
+
else customToolIds.push(toolId);
|
|
808
|
+
return {
|
|
809
|
+
builtInToolIds,
|
|
810
|
+
platformToolIds,
|
|
811
|
+
integrationToolIds,
|
|
812
|
+
mcpToolIds,
|
|
813
|
+
dataConnectionToolIds,
|
|
814
|
+
namedToolIds,
|
|
815
|
+
customToolIds
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Check if a tool ID represents a data connection tool
|
|
820
|
+
* Format: dc:{connectionId}:{action}
|
|
821
|
+
*/
|
|
822
|
+
function isDataConnectionToolId(toolId) {
|
|
823
|
+
return toolId.startsWith("dc:");
|
|
824
|
+
}
|
|
825
|
+
export { classifyVariableReference as C, SECRET_REF_PATTERN as S, validateToolHttpDestination as _, isBuiltInIntegrationToolId as a, RESERVED_INPUT_KEY_MESSAGE as b, managedDeploymentTargetSchema as c, parseToolInstanceId as d, separateToolIds as f, validateToolElicitationDefinition as g, validateManagedDeploymentTargetConfig as h, extractSecretReferencesFromAnyValue as i, mcpServerIdentityAudiencesIssue as l, toolSearchConfigSchema as m, MCPServerCategory as n, isBuiltInToolId as o, toolIntentPolicySchema as p, agentToolsConfigSchema as r, isPlatformToolId as s, CreateToolSchema as t, parseNamedToolRef as u, validateToolIdentityAudiences as v, hasNoReservedInputKeys as w, RUNTIME_PREFIXES as x, TENANCY_ASSURANCE_LEVELS as y };
|
|
826
|
+
|