@zackbart/connecta 0.24.2 → 0.24.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/AGENTS.md +18 -20
- package/CHANGELOG.md +205 -1
- package/README.md +5 -6
- package/dist/auth/bearer.js +2 -0
- package/dist/auth/downstream-oauth.d.ts +12 -1
- package/dist/auth/downstream-oauth.js +147 -35
- package/dist/branding.d.ts +31 -2
- package/dist/branding.js +116 -8
- package/dist/call-admission.d.ts +4 -0
- package/dist/call-admission.js +26 -0
- package/dist/catalog-drift.js +9 -4
- package/dist/catalog-service.d.ts +2 -0
- package/dist/catalog-service.js +25 -8
- package/dist/catalog.d.ts +2 -0
- package/dist/catalog.js +246 -121
- package/dist/connectors/api.d.ts +1 -1
- package/dist/connectors/api.js +21 -3
- package/dist/connectors/guarded-fetch.d.ts +6 -2
- package/dist/connectors/guarded-fetch.js +61 -24
- package/dist/connectors/remote-mcp.js +92 -57
- package/dist/errors.d.ts +28 -3
- package/dist/errors.js +60 -1
- package/dist/execute.js +85 -23
- package/dist/executor-result.js +3 -1
- package/dist/executors/quickjs-child.js +5 -1
- package/dist/executors/quickjs-protocol.d.ts +4 -0
- package/dist/executors/quickjs-runtime.d.ts +1 -1
- package/dist/executors/quickjs-runtime.js +38 -21
- package/dist/executors/quickjs.js +68 -27
- package/dist/index.d.ts +15 -1
- package/dist/index.js +36 -4
- package/dist/invocation.js +134 -93
- package/dist/mcp-result.js +3 -2
- package/dist/meta-tools.js +191 -36
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/view.d.ts +38 -1
- package/dist/operator-ui/view.js +71 -0
- package/dist/providers/cloudflare.d.ts +14 -2
- package/dist/providers/cloudflare.js +107 -16
- package/dist/providers/linear.d.ts +26 -4
- package/dist/providers/linear.js +19 -4
- package/dist/providers/mixpanel.d.ts +16 -3
- package/dist/providers/mixpanel.js +13 -2
- package/dist/providers/notion.d.ts +8 -1
- package/dist/providers/notion.js +83 -10
- package/dist/providers/revenuecat.d.ts +30 -4
- package/dist/providers/revenuecat.js +42 -4
- package/dist/providers/stripe.d.ts +7 -1
- package/dist/providers/stripe.js +30 -4
- package/dist/providers/vercel.js +11 -1
- package/dist/registry.d.ts +22 -2
- package/dist/registry.js +101 -13
- package/dist/routes/mcp.d.ts +4 -1
- package/dist/routes/mcp.js +84 -13
- package/dist/routes/oauth.js +4 -0
- package/dist/routes/shared.d.ts +1 -0
- package/dist/routes/shared.js +4 -4
- package/dist/server.js +15 -3
- package/dist/skills.js +6 -5
- package/dist/storage/file.d.ts +6 -2
- package/dist/storage/file.js +312 -34
- package/dist/storage/memory.js +12 -1
- package/dist/types.d.ts +37 -0
- package/dist/ui.js +18 -10
- package/dist/validate.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +193 -165
- package/documentation/auth.md +199 -145
- package/documentation/code-mode.md +433 -316
- package/documentation/meta-tools.md +363 -355
- package/examples/worker/AGENTS.md +2 -1
- package/examples/worker/README.md +12 -10
- package/examples/worker/src/index.ts +12 -15
- package/package.json +1 -2
- package/templates/node/.env.example +3 -3
- package/templates/node/AGENTS.md +5 -4
- package/templates/node/README.md +2 -1
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +23 -22
- package/documentation/call-admission.md +0 -142
- package/documentation/cloudflare.md +0 -471
- package/documentation/connector-guides.md +0 -176
- package/documentation/connectors.md +0 -389
- package/documentation/linear.md +0 -193
- package/documentation/mixpanel.md +0 -160
- package/documentation/notion.md +0 -308
- package/documentation/operations.md +0 -359
- package/documentation/operator-ui.md +0 -135
- package/documentation/optional-modules-upgrade.md +0 -243
- package/documentation/provider-conventions.md +0 -722
- package/documentation/request-admission.md +0 -170
- package/documentation/revenuecat.md +0 -305
- package/documentation/storage-and-credentials.md +0 -201
- package/documentation/stripe.md +0 -262
- package/documentation/upgrading.md +0 -754
- package/documentation/vercel.md +0 -241
package/dist/catalog.js
CHANGED
|
@@ -5,6 +5,53 @@ const MAX_COMPACT_DISCOVERY_ENUM_BYTES = MAX_COMPACT_DISCOVERY_SCHEMA_BYTES / 4;
|
|
|
5
5
|
const MAX_COMPACT_DISCOVERY_CONSTRAINT_BYTES = MAX_COMPACT_DISCOVERY_SCHEMA_BYTES / 4;
|
|
6
6
|
const schemaEncoder = new TextEncoder();
|
|
7
7
|
const COMPACT_DISCOVERY_TRUNCATION = " /* truncated */";
|
|
8
|
+
const MAX_COMPACT_DESCRIPTION_SCHEMA_BYTES = 8_192;
|
|
9
|
+
const MAX_SCHEMA_WORK = 2_000;
|
|
10
|
+
const schemaWorkExceeded = Symbol("schema work budget exceeded");
|
|
11
|
+
const schemaSizeExceeded = Symbol("schema byte budget exceeded");
|
|
12
|
+
class SchemaWork {
|
|
13
|
+
byteLimit;
|
|
14
|
+
remaining = MAX_SCHEMA_WORK;
|
|
15
|
+
truncated = false;
|
|
16
|
+
refs = new Map();
|
|
17
|
+
constructor(byteLimit = MAX_COMPACT_DISCOVERY_SCHEMA_BYTES) {
|
|
18
|
+
this.byteLimit = byteLimit;
|
|
19
|
+
}
|
|
20
|
+
visit() {
|
|
21
|
+
if (this.remaining-- <= 0)
|
|
22
|
+
throw schemaWorkExceeded;
|
|
23
|
+
}
|
|
24
|
+
text(value) {
|
|
25
|
+
// Check code units first so encoding a hostile scalar is itself bounded.
|
|
26
|
+
if (value.length > this.byteLimit ||
|
|
27
|
+
schemaEncoder.encode(value).length > this.byteLimit) {
|
|
28
|
+
throw schemaSizeExceeded;
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
json(value) {
|
|
33
|
+
// The raw-JSON fallback and const/enum values must spend the same work
|
|
34
|
+
// budget as schema nodes, including values nested inside unknown keywords.
|
|
35
|
+
const visit = this.visit.bind(this);
|
|
36
|
+
const text = this.text.bind(this);
|
|
37
|
+
const ancestors = [];
|
|
38
|
+
return this.text(JSON.stringify(value, function (key, item) {
|
|
39
|
+
visit();
|
|
40
|
+
text(key);
|
|
41
|
+
if (typeof item === "string")
|
|
42
|
+
text(item);
|
|
43
|
+
if (item !== null && typeof item === "object") {
|
|
44
|
+
while (ancestors.length && ancestors[ancestors.length - 1] !== this) {
|
|
45
|
+
ancestors.pop();
|
|
46
|
+
}
|
|
47
|
+
if (ancestors.length > 32)
|
|
48
|
+
throw schemaWorkExceeded;
|
|
49
|
+
ancestors.push(item);
|
|
50
|
+
}
|
|
51
|
+
return item;
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
8
55
|
export function summarizeDescription(text, full) {
|
|
9
56
|
return summarizeToLength(text, full, DEFAULT_DESCRIPTION_LENGTH);
|
|
10
57
|
}
|
|
@@ -315,6 +362,13 @@ function refName(ref) {
|
|
|
315
362
|
*/
|
|
316
363
|
function declaresShape(s) {
|
|
317
364
|
return (typeof s.$ref === "string" ||
|
|
365
|
+
typeof s.$dynamicRef === "string" ||
|
|
366
|
+
Array.isArray(s.allOf) ||
|
|
367
|
+
Array.isArray(s.prefixItems) ||
|
|
368
|
+
s.dependentSchemas !== undefined ||
|
|
369
|
+
s.if !== undefined ||
|
|
370
|
+
s.then !== undefined ||
|
|
371
|
+
s.else !== undefined ||
|
|
318
372
|
Array.isArray(s.oneOf) ||
|
|
319
373
|
Array.isArray(s.anyOf) ||
|
|
320
374
|
Array.isArray(s.enum) ||
|
|
@@ -342,30 +396,42 @@ function grouped(part) {
|
|
|
342
396
|
}
|
|
343
397
|
return part;
|
|
344
398
|
}
|
|
345
|
-
function renderEnum(values, byteLimit, onTruncated) {
|
|
399
|
+
function renderEnum(values, work, byteLimit, onTruncated) {
|
|
346
400
|
if (values.length === 0)
|
|
347
401
|
return "never";
|
|
348
|
-
const
|
|
349
|
-
const full = renderedValues.join(" | ");
|
|
350
|
-
if (byteLimit === undefined ||
|
|
351
|
-
schemaEncoder.encode(full).length <= byteLimit) {
|
|
352
|
-
return full;
|
|
353
|
-
}
|
|
354
|
-
onTruncated?.();
|
|
402
|
+
const limit = byteLimit ?? MAX_COMPACT_DISCOVERY_ENUM_BYTES;
|
|
355
403
|
const marker = (omitted) => `unknown /* ${omitted} enum ${omitted === 1 ? "value" : "values"} omitted */`;
|
|
356
404
|
let rendered = `(${marker(values.length)})`;
|
|
357
405
|
const prefix = [];
|
|
358
|
-
for (let index = 0; index <
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
406
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
407
|
+
let value;
|
|
408
|
+
try {
|
|
409
|
+
value = work.json(values[index]);
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
if (error !== schemaSizeExceeded)
|
|
413
|
+
throw error;
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
prefix.push(value);
|
|
417
|
+
const full = prefix.join(" | ");
|
|
418
|
+
if (schemaEncoder.encode(full).length > limit)
|
|
363
419
|
break;
|
|
364
|
-
|
|
420
|
+
if (index === values.length - 1)
|
|
421
|
+
return full;
|
|
422
|
+
const omitted = values.length - prefix.length;
|
|
423
|
+
const candidate = `(${full} | ${marker(omitted)})`;
|
|
424
|
+
if (schemaEncoder.encode(candidate).length <= limit)
|
|
425
|
+
rendered = candidate;
|
|
365
426
|
}
|
|
427
|
+
onTruncated?.();
|
|
366
428
|
return rendered;
|
|
367
429
|
}
|
|
368
430
|
function safeConstraintValue(value) {
|
|
431
|
+
if (value.length > MAX_COMPACT_DESCRIPTION_SCHEMA_BYTES) {
|
|
432
|
+
// This placeholder only participates in the byte check and is dropped whole.
|
|
433
|
+
return "x".repeat(MAX_COMPACT_DESCRIPTION_SCHEMA_BYTES + 1);
|
|
434
|
+
}
|
|
369
435
|
return JSON.stringify(value).replaceAll("*/", "*\\/");
|
|
370
436
|
}
|
|
371
437
|
function constraintEntries(schema) {
|
|
@@ -416,17 +482,36 @@ function renderConstraints(base, schema, byteLimit, onTruncated) {
|
|
|
416
482
|
: `${grouped(base)} /* ${kept.join("; ")} */`;
|
|
417
483
|
}
|
|
418
484
|
function renderSchema(schema, defs, seen, depth, options) {
|
|
485
|
+
options.work.visit();
|
|
486
|
+
return options.work.text(renderSchemaNode(schema, defs, seen, depth, options));
|
|
487
|
+
}
|
|
488
|
+
function renderSchemaNode(schema, defs, seen, depth, options) {
|
|
419
489
|
if (depth > 4)
|
|
420
490
|
return "…";
|
|
421
491
|
if (schema === null || typeof schema !== "object") {
|
|
422
|
-
return
|
|
492
|
+
return options.work.json(schema);
|
|
423
493
|
}
|
|
424
494
|
const s = schema;
|
|
425
495
|
const constrain = (rendered) => options.renderConstraints
|
|
426
496
|
? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
427
497
|
: rendered;
|
|
498
|
+
// Conditions cannot be expressed by a single static shape. Preserve the
|
|
499
|
+
// base and send callers to the exact schema instead of hiding the rules.
|
|
500
|
+
if (s.dependentSchemas !== undefined || s.if !== undefined ||
|
|
501
|
+
s.then !== undefined || s.else !== undefined) {
|
|
502
|
+
options.work.truncated = true;
|
|
503
|
+
const base = Object.create(null);
|
|
504
|
+
for (const key of propertyNames(s, options.work)) {
|
|
505
|
+
if (!["dependentSchemas", "if", "then", "else"].includes(key))
|
|
506
|
+
base[key] = s[key];
|
|
507
|
+
}
|
|
508
|
+
const rendered = declaresShape(base)
|
|
509
|
+
? renderSchema(base, defs, seen, depth, options)
|
|
510
|
+
: "unknown";
|
|
511
|
+
return `${rendered} /* conditional */`;
|
|
512
|
+
}
|
|
428
513
|
// allOf composes rather than replaces: it is checked before every other
|
|
429
|
-
// keyword, and renders the schema's own shape alongside its members instead
|
|
514
|
+
// shape keyword, and renders the schema's own shape alongside its members instead
|
|
430
515
|
// of returning early. A schema carrying both allOf and properties (the usual
|
|
431
516
|
// OpenAPI-derived "extend this base" shape, and equally legal with $ref,
|
|
432
517
|
// enum, const, or items) would otherwise silently drop whichever half lost
|
|
@@ -434,7 +519,11 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
434
519
|
// specific half, and is rendered at the current depth because its members
|
|
435
520
|
// sit at this nesting level, not one below.
|
|
436
521
|
if (Array.isArray(s.allOf)) {
|
|
437
|
-
const
|
|
522
|
+
const own = Object.create(null);
|
|
523
|
+
for (const key of propertyNames(s, options.work)) {
|
|
524
|
+
if (key !== "allOf")
|
|
525
|
+
own[key] = s[key];
|
|
526
|
+
}
|
|
438
527
|
const parts = declaresShape(own)
|
|
439
528
|
? [renderSchema(own, defs, seen, depth, options)]
|
|
440
529
|
: [];
|
|
@@ -447,16 +536,27 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
447
536
|
return parts[0];
|
|
448
537
|
return parts.map(grouped).join(" & ");
|
|
449
538
|
}
|
|
450
|
-
|
|
451
|
-
|
|
539
|
+
const reference = s.$ref ?? s.$dynamicRef;
|
|
540
|
+
if (typeof reference === "string") {
|
|
541
|
+
const dynamic = s.$ref === undefined;
|
|
542
|
+
const rawName = refName(options.work.text(reference));
|
|
543
|
+
const name = dynamic ? rawName.replace(/^#/, "") : rawName;
|
|
452
544
|
if (seen.has(name))
|
|
453
545
|
return name;
|
|
454
|
-
const target = defs
|
|
455
|
-
if (target === undefined)
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
546
|
+
const target = resolveDefinition(defs, name);
|
|
547
|
+
if (target === undefined) {
|
|
548
|
+
if (dynamic)
|
|
549
|
+
options.work.truncated = true;
|
|
550
|
+
return dynamic ? "unknown" : name;
|
|
551
|
+
}
|
|
552
|
+
const cacheKey = JSON.stringify([name, depth, [...seen]]);
|
|
553
|
+
let rendered = options.work.refs.get(cacheKey);
|
|
554
|
+
if (rendered === undefined) {
|
|
555
|
+
seen.add(name);
|
|
556
|
+
rendered = renderSchema(target, defs, seen, depth, options);
|
|
557
|
+
seen.delete(name);
|
|
558
|
+
options.work.refs.set(cacheKey, rendered);
|
|
559
|
+
}
|
|
460
560
|
return constrain(rendered);
|
|
461
561
|
}
|
|
462
562
|
const union = (s.oneOf ?? s.anyOf);
|
|
@@ -468,7 +568,7 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
468
568
|
return constrain(rendered);
|
|
469
569
|
}
|
|
470
570
|
if (Array.isArray(s.enum)) {
|
|
471
|
-
const rendered = renderEnum(s.enum, options.enumByteLimit, options.onEnumTruncated);
|
|
571
|
+
const rendered = renderEnum(s.enum, options.work, options.enumByteLimit, options.onEnumTruncated);
|
|
472
572
|
return constrain(rendered);
|
|
473
573
|
}
|
|
474
574
|
// Checked before type/properties so a discriminator like
|
|
@@ -476,10 +576,20 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
476
576
|
// JSON.stringify(undefined) returns undefined (not a string), so an explicit
|
|
477
577
|
// `const: undefined` must fall through to the regular type rendering.
|
|
478
578
|
if (s.const !== undefined) {
|
|
479
|
-
const rendered =
|
|
579
|
+
const rendered = options.work.json(s.const);
|
|
480
580
|
return constrain(rendered);
|
|
481
581
|
}
|
|
482
582
|
const type = s.type;
|
|
583
|
+
if (Array.isArray(s.prefixItems)) {
|
|
584
|
+
const parts = s.prefixItems.map((item) => renderSchema(item, defs, seen, depth + 1, options));
|
|
585
|
+
if (s.items !== false) {
|
|
586
|
+
const rest = s.items === undefined || s.items === true
|
|
587
|
+
? "unknown"
|
|
588
|
+
: renderSchema(s.items, defs, seen, depth + 1, options);
|
|
589
|
+
parts.push(`...${grouped(rest)}[]`);
|
|
590
|
+
}
|
|
591
|
+
return `[${parts.join(", ")}]`;
|
|
592
|
+
}
|
|
483
593
|
if (type === "array" || s.items) {
|
|
484
594
|
const items = s.items
|
|
485
595
|
? renderSchema(s.items, defs, seen, depth + 1, options)
|
|
@@ -488,8 +598,8 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
488
598
|
}
|
|
489
599
|
if (type === "object" || s.properties) {
|
|
490
600
|
const props = (s.properties ?? {});
|
|
491
|
-
const required = new Set((
|
|
492
|
-
const declaredKeys =
|
|
601
|
+
const required = new Set(schemaRequired(s, options.work));
|
|
602
|
+
const declaredKeys = propertyNames(props, options.work);
|
|
493
603
|
const keys = options.requiredFirst
|
|
494
604
|
? [
|
|
495
605
|
...declaredKeys.filter((key) => required.has(key)),
|
|
@@ -503,6 +613,10 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
503
613
|
const optional = required.has(key) ? "" : "?";
|
|
504
614
|
const rendered = renderSchema(props[key], defs, seen, depth + 1, options);
|
|
505
615
|
const description = props[key]?.description;
|
|
616
|
+
if (options.propertyDescriptions && typeof description === "string") {
|
|
617
|
+
options.work.text(description);
|
|
618
|
+
}
|
|
619
|
+
options.work.text(key);
|
|
506
620
|
const comment = options.propertyDescriptions && typeof description === "string"
|
|
507
621
|
? ` // ${description}`
|
|
508
622
|
: "";
|
|
@@ -511,43 +625,40 @@ function renderSchema(schema, defs, seen, depth, options) {
|
|
|
511
625
|
.join(", ")} }`;
|
|
512
626
|
}
|
|
513
627
|
if (typeof type === "string") {
|
|
514
|
-
return constrain(type);
|
|
628
|
+
return constrain(options.work.text(type));
|
|
515
629
|
}
|
|
516
630
|
if (Array.isArray(type)) {
|
|
517
|
-
const rendered = type.
|
|
631
|
+
const rendered = type.map((item) => {
|
|
632
|
+
options.work.visit();
|
|
633
|
+
return options.work.text(String(item));
|
|
634
|
+
}).join(" | ");
|
|
518
635
|
return constrain(rendered);
|
|
519
636
|
}
|
|
520
637
|
if (options.renderConstraints && constraintEntries(s).length > 0) {
|
|
521
638
|
return renderConstraints("unknown", s, options.constraintByteLimit, options.onConstraintTruncated);
|
|
522
639
|
}
|
|
523
|
-
return
|
|
640
|
+
return options.work.json(schema);
|
|
524
641
|
}
|
|
525
642
|
const compactSchemas = new WeakMap();
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
643
|
+
function resolveDefinition(schema, name) {
|
|
644
|
+
const definitions = schema.definitions;
|
|
645
|
+
const defs = schema.$defs;
|
|
646
|
+
return definitions && Object.hasOwn(definitions, name)
|
|
647
|
+
? definitions[name]
|
|
648
|
+
: defs && Object.hasOwn(defs, name) ? defs[name] : undefined;
|
|
531
649
|
}
|
|
532
650
|
/** Render and cache a compact TypeScript-like representation of JSON Schema. */
|
|
533
651
|
export function compactSchema(schema) {
|
|
652
|
+
return compactDescriptionSchema(schema).text;
|
|
653
|
+
}
|
|
654
|
+
/** Describe allows 8 KiB for property prose, with the same work cap as search. */
|
|
655
|
+
export function compactDescriptionSchema(schema) {
|
|
534
656
|
const cached = compactSchemas.get(schema);
|
|
535
657
|
if (cached)
|
|
536
658
|
return cached;
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
rendered = renderSchema(schema, defs, new Set(), 0, {
|
|
541
|
-
propertyDescriptions: true,
|
|
542
|
-
requiredFirst: false,
|
|
543
|
-
renderConstraints: true,
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
catch {
|
|
547
|
-
rendered = JSON.stringify(schema);
|
|
548
|
-
}
|
|
549
|
-
compactSchemas.set(schema, rendered);
|
|
550
|
-
return rendered;
|
|
659
|
+
const result = boundedCompactSchema(schema, true);
|
|
660
|
+
compactSchemas.set(schema, result);
|
|
661
|
+
return result;
|
|
551
662
|
}
|
|
552
663
|
const compactDiscoverySchemas = new WeakMap();
|
|
553
664
|
/**
|
|
@@ -558,8 +669,12 @@ const compactDiscoverySchemas = new WeakMap();
|
|
|
558
669
|
* Types become `unknown`: pretending a severed nested type is exact would be
|
|
559
670
|
* worse than making the existing truncation flag's recovery route explicit.
|
|
560
671
|
*/
|
|
561
|
-
function truncatedDiscoverySchema(schema) {
|
|
562
|
-
|
|
672
|
+
function truncatedDiscoverySchema(schema, work) {
|
|
673
|
+
let keys;
|
|
674
|
+
try {
|
|
675
|
+
keys = objectKeys(schema, schema, new Set(), 0, work);
|
|
676
|
+
}
|
|
677
|
+
catch { /* An exhausted walk has no reliable key inventory. */ }
|
|
563
678
|
if (!keys)
|
|
564
679
|
return `unknown${COMPACT_DISCOVERY_TRUNCATION}`;
|
|
565
680
|
const required = new Set(keys.required);
|
|
@@ -594,63 +709,44 @@ export function compactDiscoverySchema(schema) {
|
|
|
594
709
|
const cached = compactDiscoverySchemas.get(schema);
|
|
595
710
|
if (cached)
|
|
596
711
|
return cached;
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
712
|
+
const result = boundedCompactSchema(schema, false);
|
|
713
|
+
compactDiscoverySchemas.set(schema, result);
|
|
714
|
+
return result;
|
|
715
|
+
}
|
|
716
|
+
function boundedCompactSchema(schema, description) {
|
|
717
|
+
const work = new SchemaWork(description ? MAX_COMPACT_DESCRIPTION_SCHEMA_BYTES : MAX_COMPACT_DISCOVERY_SCHEMA_BYTES);
|
|
718
|
+
const options = {
|
|
719
|
+
work,
|
|
720
|
+
propertyDescriptions: description,
|
|
721
|
+
requiredFirst: !description,
|
|
722
|
+
enumByteLimit: description ? work.byteLimit : MAX_COMPACT_DISCOVERY_ENUM_BYTES,
|
|
723
|
+
onEnumTruncated: () => { work.truncated = true; },
|
|
724
|
+
renderConstraints: true,
|
|
725
|
+
constraintByteLimit: description ? work.byteLimit : MAX_COMPACT_DISCOVERY_CONSTRAINT_BYTES,
|
|
726
|
+
onConstraintTruncated: () => { work.truncated = true; },
|
|
608
727
|
};
|
|
609
728
|
try {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
rendered = renderSchema(schema, defs, new Set(), 0, {
|
|
629
|
-
...base,
|
|
630
|
-
renderConstraints: false,
|
|
631
|
-
});
|
|
632
|
-
constraintTruncated = true;
|
|
633
|
-
}
|
|
634
|
-
catch {
|
|
635
|
-
rendered = JSON.stringify(schema);
|
|
729
|
+
const text = renderSchema(schema, schema, new Set(), 0, options);
|
|
730
|
+
return { text, truncated: work.truncated };
|
|
731
|
+
}
|
|
732
|
+
catch (error) {
|
|
733
|
+
// A constraint-free retry shares the original work budget. Repeated refs
|
|
734
|
+
// are memoized only within each pass because their text includes constraints.
|
|
735
|
+
if (error === schemaSizeExceeded && !description) {
|
|
736
|
+
work.refs.clear();
|
|
737
|
+
try {
|
|
738
|
+
return {
|
|
739
|
+
text: renderSchema(schema, schema, new Set(), 0, {
|
|
740
|
+
...options,
|
|
741
|
+
renderConstraints: false,
|
|
742
|
+
}),
|
|
743
|
+
truncated: true,
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
catch { /* Fall through to a bounded key-only shape. */ }
|
|
636
747
|
}
|
|
748
|
+
return { text: truncatedDiscoverySchema(schema, work), truncated: true };
|
|
637
749
|
}
|
|
638
|
-
const bytes = schemaEncoder.encode(rendered);
|
|
639
|
-
let result;
|
|
640
|
-
if (bytes.length <= MAX_COMPACT_DISCOVERY_SCHEMA_BYTES) {
|
|
641
|
-
result = {
|
|
642
|
-
text: rendered,
|
|
643
|
-
truncated: enumTruncated || constraintTruncated,
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
else {
|
|
647
|
-
result = {
|
|
648
|
-
text: truncatedDiscoverySchema(schema),
|
|
649
|
-
truncated: true,
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
compactDiscoverySchemas.set(schema, result);
|
|
653
|
-
return result;
|
|
654
750
|
}
|
|
655
751
|
/**
|
|
656
752
|
* Walk a schema the way renderSchema does — composing `allOf` and resolving
|
|
@@ -669,9 +765,8 @@ export function compactDiscoverySchema(schema) {
|
|
|
669
765
|
export function schemaObjectKeys(schema) {
|
|
670
766
|
if (!schema)
|
|
671
767
|
return undefined;
|
|
672
|
-
const defs = defsOf(schema);
|
|
673
768
|
try {
|
|
674
|
-
return objectKeys(schema,
|
|
769
|
+
return objectKeys(schema, schema, new Set(), 0, new SchemaWork());
|
|
675
770
|
}
|
|
676
771
|
catch {
|
|
677
772
|
return undefined;
|
|
@@ -687,19 +782,27 @@ function mergedKeys(parts) {
|
|
|
687
782
|
};
|
|
688
783
|
}
|
|
689
784
|
/** The key-collecting twin of renderSchema; the branch order must match it. */
|
|
690
|
-
function objectKeys(schema, defs, seen, depth) {
|
|
785
|
+
function objectKeys(schema, defs, seen, depth, work) {
|
|
786
|
+
work.visit();
|
|
691
787
|
if (depth > 4)
|
|
692
788
|
return undefined;
|
|
693
789
|
if (schema === null || typeof schema !== "object")
|
|
694
790
|
return undefined;
|
|
695
791
|
const s = schema;
|
|
696
792
|
if (Array.isArray(s.allOf)) {
|
|
697
|
-
const
|
|
793
|
+
const own = Object.create(null);
|
|
794
|
+
for (const key of propertyNames(s, work)) {
|
|
795
|
+
if (key !== "allOf")
|
|
796
|
+
own[key] = s[key];
|
|
797
|
+
}
|
|
698
798
|
const parts = declaresShape(own)
|
|
699
|
-
? [objectKeys(own, defs, seen, depth)]
|
|
799
|
+
? [objectKeys(own, defs, seen, depth, work)]
|
|
700
800
|
: [];
|
|
701
801
|
for (const member of s.allOf) {
|
|
702
|
-
|
|
802
|
+
const keys = objectKeys(member, defs, seen, depth + 1, work);
|
|
803
|
+
if (!keys)
|
|
804
|
+
return undefined;
|
|
805
|
+
parts.push(keys);
|
|
703
806
|
}
|
|
704
807
|
// An allOf whose members are not all object shapes renders as an
|
|
705
808
|
// intersection with a non-object half; no single key list describes it.
|
|
@@ -707,15 +810,17 @@ function objectKeys(schema, defs, seen, depth) {
|
|
|
707
810
|
? mergedKeys(parts)
|
|
708
811
|
: undefined;
|
|
709
812
|
}
|
|
710
|
-
|
|
711
|
-
|
|
813
|
+
const reference = s.$ref ?? s.$dynamicRef;
|
|
814
|
+
if (typeof reference === "string") {
|
|
815
|
+
const rawName = refName(work.text(reference));
|
|
816
|
+
const name = s.$ref === undefined ? rawName.replace(/^#/, "") : rawName;
|
|
712
817
|
if (seen.has(name))
|
|
713
818
|
return undefined;
|
|
714
|
-
const target = defs
|
|
819
|
+
const target = resolveDefinition(defs, name);
|
|
715
820
|
if (target === undefined)
|
|
716
821
|
return undefined;
|
|
717
822
|
seen.add(name);
|
|
718
|
-
const resolved = objectKeys(target, defs, seen, depth);
|
|
823
|
+
const resolved = objectKeys(target, defs, seen, depth, work);
|
|
719
824
|
seen.delete(name);
|
|
720
825
|
return resolved;
|
|
721
826
|
}
|
|
@@ -725,19 +830,39 @@ function objectKeys(schema, defs, seen, depth) {
|
|
|
725
830
|
return undefined;
|
|
726
831
|
if (s.const !== undefined)
|
|
727
832
|
return undefined;
|
|
728
|
-
if (s.type === "array" || s.items)
|
|
833
|
+
if (s.type === "array" || s.items || Array.isArray(s.prefixItems))
|
|
729
834
|
return undefined;
|
|
730
835
|
if (s.type === "object" || s.properties) {
|
|
731
836
|
const props = s.properties;
|
|
732
837
|
if (props === null || Array.isArray(props) || typeof props !== "object") {
|
|
733
838
|
return { properties: [], required: [] };
|
|
734
839
|
}
|
|
840
|
+
const properties = propertyNames(props, work);
|
|
841
|
+
const declared = new Set(properties);
|
|
735
842
|
return {
|
|
736
|
-
properties
|
|
737
|
-
required:
|
|
738
|
-
? s.required.filter((key) => typeof key === "string")
|
|
739
|
-
: [],
|
|
843
|
+
properties,
|
|
844
|
+
required: schemaRequired(s, work).filter((key) => declared.has(key)),
|
|
740
845
|
};
|
|
741
846
|
}
|
|
742
847
|
return undefined;
|
|
743
848
|
}
|
|
849
|
+
function propertyNames(props, work) {
|
|
850
|
+
const names = [];
|
|
851
|
+
for (const name in props) {
|
|
852
|
+
work.visit();
|
|
853
|
+
if (Object.hasOwn(props, name))
|
|
854
|
+
names.push(work.text(name));
|
|
855
|
+
}
|
|
856
|
+
return names;
|
|
857
|
+
}
|
|
858
|
+
function schemaRequired(schema, work) {
|
|
859
|
+
if (!Array.isArray(schema.required))
|
|
860
|
+
return [];
|
|
861
|
+
const names = [];
|
|
862
|
+
for (const key of schema.required) {
|
|
863
|
+
work.visit();
|
|
864
|
+
if (typeof key === "string")
|
|
865
|
+
names.push(work.text(key));
|
|
866
|
+
}
|
|
867
|
+
return names;
|
|
868
|
+
}
|
package/dist/connectors/api.d.ts
CHANGED
|
@@ -68,5 +68,5 @@ export interface ApiOptions {
|
|
|
68
68
|
validateArgs?: boolean;
|
|
69
69
|
tools: ApiTool[];
|
|
70
70
|
}
|
|
71
|
-
/** A static connector;
|
|
71
|
+
/** A static connector; every tool passes {@link checkToolContract} first. */
|
|
72
72
|
export declare function api(id: string, opts: ApiOptions): Connector;
|
package/dist/connectors/api.js
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
import { ConnectorCallError, networkErrorCode, unavailableCallError, } from "../errors.js";
|
|
1
2
|
import { compileValidator, validateToolInput } from "../validate.js";
|
|
2
3
|
export function defined(value) {
|
|
3
4
|
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
4
5
|
}
|
|
5
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Three things a hand-written surface is refused for at construction rather
|
|
8
|
+
* than in production (#340): no description (discovery has nothing to route on
|
|
9
|
+
* and a guess costs a call), no explicit `readOnlyHint` (connecta never infers
|
|
10
|
+
* the safety class, so an unclassified tool is a deployment bug), and an
|
|
11
|
+
* `inputSchema` the validator cannot compile (declaring one is optional;
|
|
12
|
+
* declaring an unenforceable one is not). None of this reaches a proxied
|
|
13
|
+
* catalog — the contract binds the surfaces we write, not the ones we relay.
|
|
14
|
+
*/
|
|
6
15
|
function checkToolContract(id, tool) {
|
|
7
16
|
const address = `${id}.${tool.name}`;
|
|
8
17
|
if (typeof tool.description !== "string" || tool.description.trim() === "") {
|
|
@@ -19,7 +28,7 @@ function checkToolContract(id, tool) {
|
|
|
19
28
|
if (tool.inputSchema)
|
|
20
29
|
compileValidator(tool.inputSchema, { address });
|
|
21
30
|
}
|
|
22
|
-
/** A static connector;
|
|
31
|
+
/** A static connector; every tool passes {@link checkToolContract} first. */
|
|
23
32
|
export function api(id, opts) {
|
|
24
33
|
for (const t of opts.tools)
|
|
25
34
|
checkToolContract(id, t);
|
|
@@ -74,7 +83,16 @@ export function api(id, opts) {
|
|
|
74
83
|
// its first await never sits handler-less for the thenable-adoption
|
|
75
84
|
// microtask — workerd and vitest both report that gap as an unhandled
|
|
76
85
|
// rejection even though the caller catches the failure.
|
|
77
|
-
|
|
86
|
+
try {
|
|
87
|
+
return await tool.handler(input, ctx);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
// A handler owns its destinations. ctx.baseUrl is Connecta's inbound
|
|
91
|
+
// URL, so it must never masquerade as the failed downstream host.
|
|
92
|
+
if (error instanceof ConnectorCallError || !networkErrorCode(error))
|
|
93
|
+
throw error;
|
|
94
|
+
throw unavailableCallError(error);
|
|
95
|
+
}
|
|
78
96
|
},
|
|
79
97
|
};
|
|
80
98
|
}
|
|
@@ -51,7 +51,7 @@ interface GuardedResponse {
|
|
|
51
51
|
parseError: unknown;
|
|
52
52
|
}>;
|
|
53
53
|
}
|
|
54
|
-
/** Parse
|
|
54
|
+
/** Parse delta-seconds or an HTTP-date into a non-negative wait window. */
|
|
55
55
|
export declare function retryAfterMs(headers: Headers): number | undefined;
|
|
56
56
|
/**
|
|
57
57
|
* Turn one response into the provider's own result, or throw the provider's
|
|
@@ -87,6 +87,10 @@ export interface GuardedFetchOptions {
|
|
|
87
87
|
}
|
|
88
88
|
/** Send one guarded request and map its response with provider knowledge. */
|
|
89
89
|
export type GuardedTransport = <T>(request: GuardedRequest, ctx: ConnectorContext, map: GuardedResponseMapper<T>) => Promise<T>;
|
|
90
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* Build the transport a connector sends every request through. `maxResponseBytes`
|
|
92
|
+
* is required rather than defaulted: what counts as an absurd response is a fact
|
|
93
|
+
* about the API, not about HTTP.
|
|
94
|
+
*/
|
|
91
95
|
export declare function guardedFetch(options: GuardedFetchOptions): GuardedTransport;
|
|
92
96
|
export {};
|