@zackbart/connecta 0.19.0 → 0.21.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/CHANGELOG.md +107 -0
- package/README.md +3 -1
- package/bin/connecta.mjs +23 -6
- package/dist/apps-shell.d.ts +10 -12
- package/dist/apps-shell.js +29 -220
- package/dist/auth/bearer.d.ts +2 -2
- package/dist/auth/bearer.js +2 -2
- package/dist/auth/clerk.js +1 -0
- package/dist/auth/cloudflare-access.d.ts +8 -0
- package/dist/auth/cloudflare-access.js +66 -0
- package/dist/execute.d.ts +0 -7
- package/dist/execute.js +20 -123
- package/dist/index.d.ts +2 -2
- package/dist/index.js +110 -69
- package/dist/invocation.d.ts +1 -1
- package/dist/meta-tools.d.ts +0 -1
- package/dist/meta-tools.js +10 -495
- package/dist/operator-ui/generated.d.ts +2 -2
- package/dist/operator-ui/generated.js +1 -1
- package/dist/operator-ui/model.d.ts +3 -3
- package/dist/operator-ui/view.d.ts +1 -1
- package/dist/operator-ui/view.js +6 -3
- package/dist/routes/access-tokens.d.ts +1 -1
- package/dist/routes/access-tokens.js +2 -2
- package/dist/routes/activity.js +2 -2
- package/dist/routes/credentials.js +1 -1
- package/dist/routes/mcp.js +1 -1
- package/dist/routes/oauth.js +1 -1
- package/dist/routes/shared.d.ts +4 -4
- package/dist/routes/shared.js +10 -10
- package/dist/routes/ui.js +12 -9
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +11 -8
- package/dist/types.d.ts +37 -22
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +7 -4
- package/documentation/auth.md +71 -7
- package/documentation/code-mode.md +23 -23
- package/documentation/meta-tools.md +26 -50
- package/documentation/operations.md +36 -21
- package/documentation/operator-ui.md +21 -5
- package/documentation/provider-conventions.md +3 -4
- package/documentation/upgrading.md +106 -8
- package/ethos.md +3 -4
- package/examples/worker/README.md +52 -32
- package/examples/worker/src/index.ts +32 -38
- package/examples/worker/wrangler.jsonc +12 -4
- package/package.json +5 -1
- package/templates/node/package.json +1 -1
package/dist/meta-tools.js
CHANGED
|
@@ -94,480 +94,6 @@ export function alignEndToCharBoundary(bytes, offset, end, total) {
|
|
|
94
94
|
}
|
|
95
95
|
return e;
|
|
96
96
|
}
|
|
97
|
-
/** Resolve a dot-path and retain misses below every `[]` boundary. */
|
|
98
|
-
function resolvePath(value, segments) {
|
|
99
|
-
const seg = segments[0];
|
|
100
|
-
if (seg === undefined) {
|
|
101
|
-
return value === undefined
|
|
102
|
-
? { status: "unmatched" }
|
|
103
|
-
: { status: "matched", value };
|
|
104
|
-
}
|
|
105
|
-
const rest = segments.slice(1);
|
|
106
|
-
const isArr = seg.endsWith("[]");
|
|
107
|
-
const key = isArr ? seg.slice(0, -2) : seg;
|
|
108
|
-
let next = value;
|
|
109
|
-
if (key !== "") {
|
|
110
|
-
if (value === null || typeof value !== "object") {
|
|
111
|
-
return { status: "unmatched" };
|
|
112
|
-
}
|
|
113
|
-
next = value[key];
|
|
114
|
-
}
|
|
115
|
-
if (isArr) {
|
|
116
|
-
if (!Array.isArray(next))
|
|
117
|
-
return { status: "unmatched" };
|
|
118
|
-
if (next.length === 0)
|
|
119
|
-
return { status: "matched", value: [] };
|
|
120
|
-
const elements = next.map((el) => resolvePath(el, rest));
|
|
121
|
-
const matched = elements.some((element) => element.status !== "unmatched");
|
|
122
|
-
const missed = elements.some((element) => element.status !== "matched");
|
|
123
|
-
if (!matched)
|
|
124
|
-
return { status: "unmatched" };
|
|
125
|
-
return {
|
|
126
|
-
status: missed ? "partial" : "matched",
|
|
127
|
-
// Undefined placeholders retain the historical array positions in the
|
|
128
|
-
// partial value. JSON renders them as null; partialFields says they are
|
|
129
|
-
// unresolved rather than genuine downstream nulls.
|
|
130
|
-
value: elements.map((element) => element.status === "unmatched" ? undefined : element.value),
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
return resolvePath(next, rest);
|
|
134
|
-
}
|
|
135
|
-
const MAX_PROJECTION_AVAILABLE_FIELDS = 20;
|
|
136
|
-
const MAX_PROJECTION_SCHEMA_DEPTH = 12;
|
|
137
|
-
const MAX_PROJECTION_SCHEMA_NODES = 200;
|
|
138
|
-
const MAX_PROJECTION_SCHEMA_PATHS = 100;
|
|
139
|
-
const MAX_PROJECTION_PATH_CHARS = 256;
|
|
140
|
-
const MAX_PROJECTION_PATH_BYTES = 512;
|
|
141
|
-
const MAX_PROJECTION_TOTAL_PATH_CHARS = 512;
|
|
142
|
-
const MAX_PROJECTION_TOTAL_PATH_BYTES = 768;
|
|
143
|
-
const JSON_SCHEMA_TYPES = new Set([
|
|
144
|
-
"array",
|
|
145
|
-
"boolean",
|
|
146
|
-
"integer",
|
|
147
|
-
"null",
|
|
148
|
-
"number",
|
|
149
|
-
"object",
|
|
150
|
-
"string",
|
|
151
|
-
]);
|
|
152
|
-
const NON_SEMANTIC_REF_SIBLINGS = new Set([
|
|
153
|
-
"$anchor",
|
|
154
|
-
"$comment",
|
|
155
|
-
"$defs",
|
|
156
|
-
"$id",
|
|
157
|
-
"$ref",
|
|
158
|
-
"$schema",
|
|
159
|
-
"default",
|
|
160
|
-
"definitions",
|
|
161
|
-
"deprecated",
|
|
162
|
-
"description",
|
|
163
|
-
"examples",
|
|
164
|
-
"readOnly",
|
|
165
|
-
"title",
|
|
166
|
-
"writeOnly",
|
|
167
|
-
]);
|
|
168
|
-
function localSchemaRef(root, ref) {
|
|
169
|
-
if (ref === "#")
|
|
170
|
-
return root;
|
|
171
|
-
if (!ref.startsWith("#/") ||
|
|
172
|
-
ref.length > 2_048 ||
|
|
173
|
-
/~(?![01])/.test(ref)) {
|
|
174
|
-
return undefined;
|
|
175
|
-
}
|
|
176
|
-
const segments = ref.slice(2).split("/");
|
|
177
|
-
if (segments.length > MAX_PROJECTION_SCHEMA_DEPTH)
|
|
178
|
-
return undefined;
|
|
179
|
-
let current = root;
|
|
180
|
-
for (const encoded of segments) {
|
|
181
|
-
if (current === null || typeof current !== "object")
|
|
182
|
-
return undefined;
|
|
183
|
-
const key = encoded.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
184
|
-
if (!Object.prototype.hasOwnProperty.call(current, key))
|
|
185
|
-
return undefined;
|
|
186
|
-
current = current[key];
|
|
187
|
-
}
|
|
188
|
-
return current;
|
|
189
|
-
}
|
|
190
|
-
function hasSchemaType(schema, wanted) {
|
|
191
|
-
return (schema.type === wanted ||
|
|
192
|
-
(Array.isArray(schema.type) && schema.type.includes(wanted)));
|
|
193
|
-
}
|
|
194
|
-
function selectableFieldName(name) {
|
|
195
|
-
return name.length > 0 && !name.includes(".") && !name.endsWith("[]");
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Collect selectable output paths without trusting a schema more than JSON
|
|
199
|
-
* Schema permits. Traversal is iterative and budgeted before sorting or
|
|
200
|
-
* rendering, so a cyclic, extremely deep, or extremely broad downstream
|
|
201
|
-
* schema cannot turn projection feedback into unbounded host work.
|
|
202
|
-
*/
|
|
203
|
-
function analyzeSchemaFields(root) {
|
|
204
|
-
const pending = [
|
|
205
|
-
{ schema: root, prefix: "", depth: 0, ancestors: new Set() },
|
|
206
|
-
];
|
|
207
|
-
const paths = new Set();
|
|
208
|
-
let nodes = 0;
|
|
209
|
-
let totalPathChars = 0;
|
|
210
|
-
let totalPathBytes = 0;
|
|
211
|
-
let complete = true;
|
|
212
|
-
let truncated = false;
|
|
213
|
-
const addPath = (path) => {
|
|
214
|
-
if (paths.has(path))
|
|
215
|
-
return true;
|
|
216
|
-
// Check UTF-16 length before encoding, so a hostile multi-megabyte key
|
|
217
|
-
// never causes a same-sized temporary allocation merely to reject it.
|
|
218
|
-
if (path.length > MAX_PROJECTION_PATH_CHARS ||
|
|
219
|
-
totalPathChars + path.length > MAX_PROJECTION_TOTAL_PATH_CHARS) {
|
|
220
|
-
complete = false;
|
|
221
|
-
truncated = true;
|
|
222
|
-
return false;
|
|
223
|
-
}
|
|
224
|
-
const pathBytes = enc.encode(path).length;
|
|
225
|
-
if (pathBytes > MAX_PROJECTION_PATH_BYTES ||
|
|
226
|
-
totalPathBytes + pathBytes > MAX_PROJECTION_TOTAL_PATH_BYTES ||
|
|
227
|
-
paths.size >= MAX_PROJECTION_SCHEMA_PATHS) {
|
|
228
|
-
complete = false;
|
|
229
|
-
truncated = true;
|
|
230
|
-
return false;
|
|
231
|
-
}
|
|
232
|
-
paths.add(path);
|
|
233
|
-
totalPathChars += path.length;
|
|
234
|
-
totalPathBytes += pathBytes;
|
|
235
|
-
return true;
|
|
236
|
-
};
|
|
237
|
-
const enqueue = (item) => {
|
|
238
|
-
if (nodes + pending.length >= MAX_PROJECTION_SCHEMA_NODES) {
|
|
239
|
-
complete = false;
|
|
240
|
-
truncated = true;
|
|
241
|
-
return false;
|
|
242
|
-
}
|
|
243
|
-
pending.push(item);
|
|
244
|
-
return true;
|
|
245
|
-
};
|
|
246
|
-
while (pending.length > 0) {
|
|
247
|
-
const item = pending.pop();
|
|
248
|
-
if (item.depth > MAX_PROJECTION_SCHEMA_DEPTH) {
|
|
249
|
-
complete = false;
|
|
250
|
-
truncated = true;
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
if (++nodes > MAX_PROJECTION_SCHEMA_NODES) {
|
|
254
|
-
complete = false;
|
|
255
|
-
truncated = true;
|
|
256
|
-
break;
|
|
257
|
-
}
|
|
258
|
-
if (item.schema === false)
|
|
259
|
-
continue;
|
|
260
|
-
if (item.schema === true ||
|
|
261
|
-
item.schema === null ||
|
|
262
|
-
typeof item.schema !== "object" ||
|
|
263
|
-
item.ancestors.has(item.schema)) {
|
|
264
|
-
complete = false;
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
const schema = item.schema;
|
|
268
|
-
const ancestors = new Set(item.ancestors).add(item.schema);
|
|
269
|
-
let recognized = false;
|
|
270
|
-
if (schema.type !== undefined &&
|
|
271
|
-
!((typeof schema.type === "string" &&
|
|
272
|
-
JSON_SCHEMA_TYPES.has(schema.type)) ||
|
|
273
|
-
(Array.isArray(schema.type) &&
|
|
274
|
-
schema.type.length > 0 &&
|
|
275
|
-
schema.type.every((type) => typeof type === "string" && JSON_SCHEMA_TYPES.has(type))))) {
|
|
276
|
-
complete = false;
|
|
277
|
-
}
|
|
278
|
-
if (schema.$ref !== undefined) {
|
|
279
|
-
recognized = true;
|
|
280
|
-
let hasSemanticSiblings = false;
|
|
281
|
-
for (const key in schema) {
|
|
282
|
-
if (Object.prototype.hasOwnProperty.call(schema, key) &&
|
|
283
|
-
!NON_SEMANTIC_REF_SIBLINGS.has(key)) {
|
|
284
|
-
hasSemanticSiblings = true;
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
if (hasSemanticSiblings) {
|
|
289
|
-
// Modern JSON Schema applies $ref siblings as an intersection. A
|
|
290
|
-
// compact field walker cannot prove that intersection's selectable
|
|
291
|
-
// paths, so do not publish paths from either half as available.
|
|
292
|
-
complete = false;
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
const target = typeof schema.$ref === "string"
|
|
296
|
-
? localSchemaRef(root, schema.$ref)
|
|
297
|
-
: undefined;
|
|
298
|
-
if (target === undefined)
|
|
299
|
-
complete = false;
|
|
300
|
-
else {
|
|
301
|
-
enqueue({
|
|
302
|
-
schema: target,
|
|
303
|
-
prefix: item.prefix,
|
|
304
|
-
depth: item.depth + 1,
|
|
305
|
-
ancestors,
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
for (const keyword of ["allOf", "anyOf", "oneOf"]) {
|
|
310
|
-
const variants = schema[keyword];
|
|
311
|
-
if (!Array.isArray(variants))
|
|
312
|
-
continue;
|
|
313
|
-
recognized = true;
|
|
314
|
-
// Combining schemas can close or conditionally expose fields in ways
|
|
315
|
-
// this compact recovery walker intentionally does not prove.
|
|
316
|
-
complete = false;
|
|
317
|
-
for (const variant of variants) {
|
|
318
|
-
if (!enqueue({
|
|
319
|
-
schema: variant,
|
|
320
|
-
prefix: item.prefix,
|
|
321
|
-
depth: item.depth + 1,
|
|
322
|
-
ancestors,
|
|
323
|
-
})) {
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
if (truncated)
|
|
329
|
-
break;
|
|
330
|
-
const properties = schema.properties;
|
|
331
|
-
const propertyRecord = properties !== null &&
|
|
332
|
-
typeof properties === "object" &&
|
|
333
|
-
!Array.isArray(properties)
|
|
334
|
-
? properties
|
|
335
|
-
: undefined;
|
|
336
|
-
if (properties !== undefined && propertyRecord === undefined) {
|
|
337
|
-
complete = false;
|
|
338
|
-
}
|
|
339
|
-
const objectShape = hasSchemaType(schema, "object") || propertyRecord !== undefined;
|
|
340
|
-
if (objectShape) {
|
|
341
|
-
recognized = true;
|
|
342
|
-
const patterns = schema.patternProperties;
|
|
343
|
-
let hasPatterns = false;
|
|
344
|
-
if (patterns !== undefined &&
|
|
345
|
-
(patterns === null ||
|
|
346
|
-
typeof patterns !== "object" ||
|
|
347
|
-
Array.isArray(patterns))) {
|
|
348
|
-
complete = false;
|
|
349
|
-
}
|
|
350
|
-
else if (patterns !== undefined) {
|
|
351
|
-
for (const key in patterns) {
|
|
352
|
-
if (Object.prototype.hasOwnProperty.call(patterns, key)) {
|
|
353
|
-
hasPatterns = true;
|
|
354
|
-
break;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
if (schema.additionalProperties !== false || hasPatterns) {
|
|
359
|
-
complete = false;
|
|
360
|
-
}
|
|
361
|
-
if (propertyRecord) {
|
|
362
|
-
for (const key in propertyRecord) {
|
|
363
|
-
if (!Object.prototype.hasOwnProperty.call(propertyRecord, key)) {
|
|
364
|
-
continue;
|
|
365
|
-
}
|
|
366
|
-
if (++nodes > MAX_PROJECTION_SCHEMA_NODES) {
|
|
367
|
-
complete = false;
|
|
368
|
-
truncated = true;
|
|
369
|
-
break;
|
|
370
|
-
}
|
|
371
|
-
if (key.length > MAX_PROJECTION_PATH_CHARS) {
|
|
372
|
-
complete = false;
|
|
373
|
-
truncated = true;
|
|
374
|
-
break;
|
|
375
|
-
}
|
|
376
|
-
if (!selectableFieldName(key)) {
|
|
377
|
-
complete = false;
|
|
378
|
-
continue;
|
|
379
|
-
}
|
|
380
|
-
const child = propertyRecord[key];
|
|
381
|
-
// A false property schema forbids the property; advertising its name
|
|
382
|
-
// as selectable would turn an impossible value into a valid hint.
|
|
383
|
-
if (child === false)
|
|
384
|
-
continue;
|
|
385
|
-
const path = item.prefix ? `${item.prefix}.${key}` : key;
|
|
386
|
-
if (!addPath(path))
|
|
387
|
-
break;
|
|
388
|
-
if (!enqueue({
|
|
389
|
-
schema: child,
|
|
390
|
-
prefix: path,
|
|
391
|
-
depth: item.depth + 1,
|
|
392
|
-
ancestors,
|
|
393
|
-
})) {
|
|
394
|
-
break;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
if (truncated)
|
|
400
|
-
break;
|
|
401
|
-
const arrayShape = hasSchemaType(schema, "array") ||
|
|
402
|
-
schema.items !== undefined ||
|
|
403
|
-
schema.prefixItems !== undefined;
|
|
404
|
-
if (arrayShape) {
|
|
405
|
-
recognized = true;
|
|
406
|
-
const arrayPath = `${item.prefix}[]`;
|
|
407
|
-
if (addPath(arrayPath)) {
|
|
408
|
-
if (Array.isArray(schema.prefixItems)) {
|
|
409
|
-
complete = false;
|
|
410
|
-
for (const child of schema.prefixItems) {
|
|
411
|
-
if (!enqueue({
|
|
412
|
-
schema: child,
|
|
413
|
-
prefix: arrayPath,
|
|
414
|
-
depth: item.depth + 1,
|
|
415
|
-
ancestors,
|
|
416
|
-
})) {
|
|
417
|
-
break;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
if (schema.items === undefined) {
|
|
422
|
-
if (!Array.isArray(schema.prefixItems))
|
|
423
|
-
complete = false;
|
|
424
|
-
}
|
|
425
|
-
else if (schema.items === true || Array.isArray(schema.items)) {
|
|
426
|
-
complete = false;
|
|
427
|
-
const children = Array.isArray(schema.items)
|
|
428
|
-
? schema.items
|
|
429
|
-
: [];
|
|
430
|
-
for (const child of children) {
|
|
431
|
-
if (!enqueue({
|
|
432
|
-
schema: child,
|
|
433
|
-
prefix: arrayPath,
|
|
434
|
-
depth: item.depth + 1,
|
|
435
|
-
ancestors,
|
|
436
|
-
})) {
|
|
437
|
-
break;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
else if (schema.items !== false) {
|
|
442
|
-
enqueue({
|
|
443
|
-
schema: schema.items,
|
|
444
|
-
prefix: arrayPath,
|
|
445
|
-
depth: item.depth + 1,
|
|
446
|
-
ancestors,
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
const types = Array.isArray(schema.type)
|
|
452
|
-
? schema.type
|
|
453
|
-
: schema.type === undefined
|
|
454
|
-
? []
|
|
455
|
-
: [schema.type];
|
|
456
|
-
const primitiveOnly = types.length > 0 &&
|
|
457
|
-
types.every((type) => type === "string" ||
|
|
458
|
-
type === "number" ||
|
|
459
|
-
type === "integer" ||
|
|
460
|
-
type === "boolean" ||
|
|
461
|
-
type === "null");
|
|
462
|
-
if (!recognized && !primitiveOnly)
|
|
463
|
-
complete = false;
|
|
464
|
-
if (truncated)
|
|
465
|
-
break;
|
|
466
|
-
}
|
|
467
|
-
return {
|
|
468
|
-
paths: [...paths].sort(),
|
|
469
|
-
complete,
|
|
470
|
-
truncated,
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
function schemaProjectionFeedback(outputSchema, unmatchedFields) {
|
|
474
|
-
const analysis = analyzeSchemaFields(outputSchema);
|
|
475
|
-
const available = new Set(analysis.paths);
|
|
476
|
-
const invalidFields = analysis.complete
|
|
477
|
-
? unmatchedFields.filter((field) => !available.has(field))
|
|
478
|
-
: [];
|
|
479
|
-
const missingArrayMarker = unmatchedFields.some((field) => analysis.paths.some((availableField) => availableField.includes("[]") &&
|
|
480
|
-
availableField.replaceAll("[]", "") === field));
|
|
481
|
-
return {
|
|
482
|
-
...(missingArrayMarker
|
|
483
|
-
? {
|
|
484
|
-
hint: 'Traverse arrays with [] after the array field name, for example "results[].id".',
|
|
485
|
-
}
|
|
486
|
-
: {}),
|
|
487
|
-
schemaDeclared: true,
|
|
488
|
-
schemaCoverage: analysis.complete ? "complete" : "partial",
|
|
489
|
-
...(invalidFields.length > 0 ? { invalidFields } : {}),
|
|
490
|
-
...(analysis.paths.length > 0
|
|
491
|
-
? {
|
|
492
|
-
availableFields: analysis.paths.slice(0, MAX_PROJECTION_AVAILABLE_FIELDS),
|
|
493
|
-
}
|
|
494
|
-
: {}),
|
|
495
|
-
...(analysis.truncated ||
|
|
496
|
-
analysis.paths.length > MAX_PROJECTION_AVAILABLE_FIELDS
|
|
497
|
-
? { availableFieldsTruncated: true }
|
|
498
|
-
: {}),
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
/** Select the given dot-paths, retaining both matches and exact misses. */
|
|
502
|
-
function applyFields(value, fields) {
|
|
503
|
-
const out = {};
|
|
504
|
-
const unmatchedFields = [];
|
|
505
|
-
const partialFields = [];
|
|
506
|
-
for (const path of fields) {
|
|
507
|
-
const resolved = resolvePath(value, path.split("."));
|
|
508
|
-
if (resolved.status === "unmatched") {
|
|
509
|
-
unmatchedFields.push(path);
|
|
510
|
-
}
|
|
511
|
-
else {
|
|
512
|
-
out[path] = resolved.value;
|
|
513
|
-
if (resolved.status === "partial")
|
|
514
|
-
partialFields.push(path);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
return { data: out, unmatchedFields, partialFields };
|
|
518
|
-
}
|
|
519
|
-
/**
|
|
520
|
-
* Keep the historical flat projection when every path resolves. A miss gets a
|
|
521
|
-
* wrapper with a reserved discriminator so neither `{}` nor downstream fields
|
|
522
|
-
* named `data` / `projection` can be mistaken for projection feedback.
|
|
523
|
-
*/
|
|
524
|
-
function projectionValue(value, fields, outputSchema) {
|
|
525
|
-
const projected = applyFields(value, fields);
|
|
526
|
-
// `$connecta` is reserved at the top level of a projection. Even a fully
|
|
527
|
-
// matched downstream field with that exact name is escaped below `data`, so
|
|
528
|
-
// no user-controlled value can impersonate Connecta's discriminator.
|
|
529
|
-
const reservedCollision = Object.prototype.hasOwnProperty.call(projected.data, "$connecta");
|
|
530
|
-
if (projected.unmatchedFields.length === 0 &&
|
|
531
|
-
projected.partialFields.length === 0 &&
|
|
532
|
-
!reservedCollision) {
|
|
533
|
-
return projected.data;
|
|
534
|
-
}
|
|
535
|
-
const problemFields = [
|
|
536
|
-
...projected.unmatchedFields,
|
|
537
|
-
...projected.partialFields,
|
|
538
|
-
];
|
|
539
|
-
return {
|
|
540
|
-
data: projected.data,
|
|
541
|
-
$connecta: {
|
|
542
|
-
type: "field_projection",
|
|
543
|
-
unmatchedFields: projected.unmatchedFields,
|
|
544
|
-
...(projected.partialFields.length > 0
|
|
545
|
-
? { partialFields: projected.partialFields }
|
|
546
|
-
: {}),
|
|
547
|
-
...(outputSchema
|
|
548
|
-
? schemaProjectionFeedback(outputSchema, problemFields)
|
|
549
|
-
: {}),
|
|
550
|
-
},
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
/** Apply fields to each JSON-parseable text block; non-JSON blocks pass through. */
|
|
554
|
-
function applyFieldsToContent(content, fields, outputSchema) {
|
|
555
|
-
return content.map((b) => {
|
|
556
|
-
if (b.type !== "text")
|
|
557
|
-
return b;
|
|
558
|
-
let parsed;
|
|
559
|
-
try {
|
|
560
|
-
parsed = JSON.parse(b.text);
|
|
561
|
-
}
|
|
562
|
-
catch {
|
|
563
|
-
return b;
|
|
564
|
-
}
|
|
565
|
-
return {
|
|
566
|
-
...b,
|
|
567
|
-
text: JSON.stringify(projectionValue(parsed, fields, outputSchema)),
|
|
568
|
-
};
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
97
|
// --- result-size guard + get_result (feature 1) ---------------------------
|
|
572
98
|
/**
|
|
573
99
|
* The one serialization every result guard measures, stashes, and pages: JSON
|
|
@@ -595,7 +121,7 @@ async function stashResult(text, results, totalBytes) {
|
|
|
595
121
|
truncated: true,
|
|
596
122
|
resultId: id,
|
|
597
123
|
totalBytes,
|
|
598
|
-
hint: "use get_result {id, offset} to page
|
|
124
|
+
hint: "use get_result {id, offset} to page the complete direct-call result",
|
|
599
125
|
nextAction: {
|
|
600
126
|
tool: "get_result",
|
|
601
127
|
arguments: { id, offset: 0 },
|
|
@@ -734,7 +260,6 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
734
260
|
/** MCP adapter: shared invocation semantics plus MCP-only result shaping. */
|
|
735
261
|
async function runCall(call, source, options = {}) {
|
|
736
262
|
const results = registry.resultsStorage();
|
|
737
|
-
const fields = call.fields && call.fields.length > 0 ? call.fields : null;
|
|
738
263
|
const timeoutMs = normalizeTimeoutMs(call.timeoutMs) ?? defaultToolTimeoutMs;
|
|
739
264
|
const outcome = await invocation.invoke(call.address, call.args ?? {}, {
|
|
740
265
|
source,
|
|
@@ -763,25 +288,18 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
763
288
|
...(truncated ? { friction: "result_too_large" } : {}),
|
|
764
289
|
});
|
|
765
290
|
if (call.resultMode === "value") {
|
|
766
|
-
let value =
|
|
767
|
-
? projectionValue(result, fields, resolved.definition.outputSchema)
|
|
768
|
-
: result;
|
|
291
|
+
let value = result;
|
|
769
292
|
const guarded = await guardValue(value, results, cap);
|
|
770
293
|
value = guarded.result;
|
|
771
294
|
return processed(jsonResult({ ok: true, data: value }), guarded.truncated, { value });
|
|
772
295
|
}
|
|
773
296
|
if (resolved.connector.kind === "mcp") {
|
|
774
297
|
const mcpResult = result;
|
|
775
|
-
|
|
776
|
-
if (fields) {
|
|
777
|
-
content = applyFieldsToContent(content, fields, resolved.definition.outputSchema);
|
|
778
|
-
}
|
|
298
|
+
const content = mcpResult?.content ?? [];
|
|
779
299
|
const guarded = await guardContent(content, results, cap);
|
|
780
300
|
return processed(guarded.result, guarded.truncated);
|
|
781
301
|
}
|
|
782
|
-
const value =
|
|
783
|
-
? projectionValue(result, fields, resolved.definition.outputSchema)
|
|
784
|
-
: result;
|
|
302
|
+
const value = result;
|
|
785
303
|
const guarded = await guardText(serializeResultText(value), results, cap);
|
|
786
304
|
return processed(guarded.result, guarded.truncated, { value });
|
|
787
305
|
},
|
|
@@ -867,7 +385,7 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
867
385
|
},
|
|
868
386
|
async callDestructiveTool(args) {
|
|
869
387
|
// `reason` is read by the host's approval view and stops there — runCall
|
|
870
|
-
// forwards only the call
|
|
388
|
+
// forwards only the call arguments, so it never reaches the connector.
|
|
871
389
|
return (await runCall(args, "call_destructive_tool", { allowDestructive: true })).toolResult;
|
|
872
390
|
},
|
|
873
391
|
async getResult(args) {
|
|
@@ -997,12 +515,12 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
997
515
|
},
|
|
998
516
|
};
|
|
999
517
|
}
|
|
1000
|
-
const SEARCH_DESC = `Use top-level search for
|
|
1001
|
-
const CALL_DESC = 'Call one tool explicitly annotated readOnlyHint: true. Use execute_code for multiple, dependent, or reduced read-only
|
|
518
|
+
const SEARCH_DESC = `Use top-level search for catalog inspection or approval-required work before call_destructive_tool. Unknown-address read-only work belongs in one execute_code program that searches, calls, and returns the answer. Use 2–4 action/object terms and includeSchemas="compact"; the default limit is ${DEFAULT_SEARCH_LIMIT}. Set connector when known. safety="readOnly" finds direct or program calls; "approvalRequired" finds the fail-closed complement. These filters grant no authority. Empty query browses.`;
|
|
519
|
+
const CALL_DESC = 'Call one known-address tool explicitly annotated readOnlyHint: true. Use execute_code for unknown-address, multiple, dependent, or reduced read-only work. Unannotated or write-capable tools fail closed to call_destructive_tool. A truncated result carries a get_result action.';
|
|
1002
520
|
const CALL_DESTRUCTIVE_DESC = "Call any tool not explicitly annotated readOnlyHint: true. Include a short reason for the human reviewer after checking the schema and consequences. The reason grants no authority and is not sent downstream.";
|
|
1003
521
|
const GET_RESULT_DESC = "Page a truncated direct-call result by id and byte offset. A program result is never paged; reduce it inside execute_code. Returns text, offset, nextOffset when more remains, and totalBytes.";
|
|
1004
522
|
const AUTHORIZE_DESC = "Use after auth_required. Returns an OAuth or operator-credential handoff, or reports required deployment configuration. force=true restarts OAuth only; this tool never accepts credentials.";
|
|
1005
|
-
const SKILLS_DESC = 'List or fetch on-demand guidance. Fetch usage
|
|
523
|
+
const SKILLS_DESC = 'List or fetch on-demand guidance. Fetch usage only when the always-loaded instructions are insufficient or a program needs repair.';
|
|
1006
524
|
/**
|
|
1007
525
|
* Sentences appended to a meta-tool description only when this connection
|
|
1008
526
|
* actually has connector guides. Tool descriptions are always-loaded context,
|
|
@@ -1043,7 +561,6 @@ const READ_ONLY_LOCAL = {
|
|
|
1043
561
|
const CALL_INPUT_SCHEMA = {
|
|
1044
562
|
address: z.string(),
|
|
1045
563
|
args: z.record(z.string(), z.unknown()).optional(),
|
|
1046
|
-
fields: z.array(z.string()).optional(),
|
|
1047
564
|
resultMode: z.enum(["mcp", "value"]).optional(),
|
|
1048
565
|
timeoutMs: z.number().int().positive().optional(),
|
|
1049
566
|
maxRetries: z.number().int().min(0).max(2).optional(),
|
|
@@ -1094,10 +611,8 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
1094
611
|
// call_tool admits only tools that are themselves explicitly read-only;
|
|
1095
612
|
// anything else is refused and routed to call_destructive_tool.
|
|
1096
613
|
annotations: READ_ONLY_REMOTE,
|
|
1097
|
-
//
|
|
1098
|
-
|
|
1099
|
-
// tool and this handler repeats ordinary fail-closed read admission.
|
|
1100
|
-
_meta: { ui: { visibility: ["model", "app"] } },
|
|
614
|
+
// Omission defaults to model + app. Display-only views may call no tool.
|
|
615
|
+
_meta: { ui: { visibility: ["model"] } },
|
|
1101
616
|
}, async (args) => mt.callTool(args));
|
|
1102
617
|
server.registerTool("call_destructive_tool", {
|
|
1103
618
|
description: describedFor(registry, CALL_DESTRUCTIVE_DESC, "destructive"),
|