@cerebro-labs/agentnava-sdk 0.26.0 → 0.44.1

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.
Files changed (62) hide show
  1. package/README.md +204 -23
  2. package/account.d.ts +154 -0
  3. package/account.js +219 -0
  4. package/account.js.map +1 -0
  5. package/agent.d.ts +217 -88
  6. package/agent.js +295 -125
  7. package/agent.js.map +1 -1
  8. package/apps.generated.d.ts +154 -0
  9. package/apps.generated.js +152 -0
  10. package/apps.generated.js.map +1 -0
  11. package/attach.d.ts +25 -26
  12. package/attach.js +53 -66
  13. package/attach.js.map +1 -1
  14. package/connection.d.ts +79 -82
  15. package/connection.js +67 -134
  16. package/connection.js.map +1 -1
  17. package/conversation.d.ts +55 -35
  18. package/conversation.js +70 -50
  19. package/conversation.js.map +1 -1
  20. package/database.d.ts +53 -0
  21. package/database.js +69 -0
  22. package/database.js.map +1 -0
  23. package/end-user.d.ts +58 -47
  24. package/end-user.js +62 -72
  25. package/end-user.js.map +1 -1
  26. package/errors.d.ts +2 -1
  27. package/errors.js +2 -1
  28. package/errors.js.map +1 -1
  29. package/http.d.ts +5 -0
  30. package/http.js +109 -8
  31. package/http.js.map +1 -1
  32. package/import-tools.d.ts +48 -33
  33. package/import-tools.js +281 -127
  34. package/import-tools.js.map +1 -1
  35. package/index.d.ts +5 -2
  36. package/index.js +9 -2
  37. package/index.js.map +1 -1
  38. package/load-agent-dir.js +28 -5
  39. package/load-agent-dir.js.map +1 -1
  40. package/package.json +5 -1
  41. package/secret.d.ts +1 -1
  42. package/secret.js +1 -1
  43. package/secret.js.map +1 -1
  44. package/templates-inline.js +1 -1
  45. package/templates-inline.js.map +1 -1
  46. package/turn.d.ts +1 -1
  47. package/turn.js +77 -29
  48. package/turn.js.map +1 -1
  49. package/types.d.ts +409 -83
  50. package/types.js.map +1 -1
  51. package/version.d.ts +1 -1
  52. package/version.js +1 -1
  53. package/version.js.map +1 -1
  54. package/workflows.d.ts +10 -0
  55. package/workflows.js +16 -0
  56. package/workflows.js.map +1 -1
  57. package/workspace.d.ts +80 -62
  58. package/workspace.js +92 -75
  59. package/workspace.js.map +1 -1
  60. package/connection-status.d.ts +0 -28
  61. package/connection-status.js +0 -61
  62. package/connection-status.js.map +0 -1
package/import-tools.js CHANGED
@@ -199,8 +199,8 @@ class Draft {
199
199
  * never-sent `order_id_2` in `params`, which is the very "declared and never
200
200
  * sent" defect this class is meant to prevent. So a repeat returns the same
201
201
  * name and merges the richer definition over the first guess. */
202
- param(params, wireName, def, tool) {
203
- const key = `${tool}\u0000${wireName}`;
202
+ param(params, wireName, def, tool, identity = wireName) {
203
+ const key = `${tool}\u0000${identity}`;
204
204
  const already = this.#paramNames.get(key);
205
205
  if (already !== undefined) {
206
206
  params[already] = { ...params[already], ...def };
@@ -426,146 +426,300 @@ export function fromPostman(collection, options = {}) {
426
426
  return draft.finish(options);
427
427
  }
428
428
  const OPENAPI_METHODS = ['get', 'post', 'put', 'delete', 'patch'];
429
- function openApiType(t) {
430
- if (t === 'integer' || t === 'number')
431
- return 'number';
432
- if (t === 'boolean')
433
- return 'boolean';
434
- return 'string';
429
+ function dereference(value, doc, seen = new Set(), validateReference) {
430
+ if (!value.$ref)
431
+ return value;
432
+ validateReference?.(value);
433
+ if (!value.$ref.startsWith('#/') || seen.has(value.$ref))
434
+ throw Error('Only acyclic local OpenAPI references are supported');
435
+ seen.add(value.$ref);
436
+ let target = doc;
437
+ for (const key of value.$ref.slice(2).split('/').map(x => x.replace(/~1/g, '/').replace(/~0/g, '~'))) {
438
+ if (!target || typeof target !== 'object' || !Object.hasOwn(target, key))
439
+ throw Error(`Unresolved OpenAPI reference ${value.$ref}`);
440
+ target = target[key];
441
+ }
442
+ if (!target || typeof target !== 'object' || Array.isArray(target))
443
+ throw Error('OpenAPI reference must resolve to an object');
444
+ return dereference(target, doc, seen, validateReference);
435
445
  }
436
- /**
437
- * Draft tools from an OpenAPI 3 or Swagger 2 document.
438
- *
439
- * WHAT THIS GIVES THAT POSTMAN DOES NOT: real parameter types and required
440
- * flags, which is exactly the shape `HttpToolParam` wants, and an enum where the
441
- * document declares one. FastAPI publishes this at `/openapi.json` without
442
- * anybody writing it, so for a FastAPI shop this is usually the richer source.
443
- *
444
- * WHAT IT STILL DOES NOT GIVE: a description a model can choose on. `summary`
445
- * is written for a developer reading a reference. Read the warnings.
446
- */
446
+ const OPENAPI_SCHEMA_FIELDS = new Set(['type', 'properties', 'items', 'required', 'enum', 'nullable', 'description', 'additionalProperties', '$ref', 'title', 'format', 'example', 'examples', 'default', 'readOnly', 'writeOnly', 'deprecated']);
447
+ /** Schema Object siblings apply alongside the referenced schema in OpenAPI
448
+ * 3.1. Reject them at every reference hop until that intersection is supported.
449
+ * Parameter and request-body Reference Objects keep their separate semantics. */
450
+ function checkSchemaReference(schema) {
451
+ const sibling = Object.keys(schema).find(key => key !== '$ref');
452
+ if (sibling !== undefined)
453
+ throw Error(`Schema keyword ${sibling} alongside $ref is not supported`);
454
+ }
455
+ function checkSchemaKeywords(schema) {
456
+ for (const key of Object.keys(schema)) {
457
+ if (!OPENAPI_SCHEMA_FIELDS.has(key) && !key.startsWith('x-')) {
458
+ throw Error(`Unsupported JSON schema keyword ${key}`);
459
+ }
460
+ }
461
+ }
462
+ /** Preserve the object composition used by generated request bodies. This is
463
+ * not a general schema intersection: conflicting properties and closed-object
464
+ * extensions need explicit configuration rather than a weakened contract. */
465
+ function resolveOpenApiSchema(raw, doc, depth) {
466
+ if (depth > 12)
467
+ throw Error('OpenAPI schema is recursive or exceeds 12 levels');
468
+ const schema = dereference(raw, doc, new Set(), checkSchemaReference);
469
+ if (!schema.allOf)
470
+ return schema;
471
+ const { allOf, ...own } = schema;
472
+ if (!allOf.length)
473
+ throw Error('An allOf schema needs at least one member');
474
+ const parts = [...allOf.map(part => resolveOpenApiSchema(part, doc, depth + 1)), own];
475
+ const properties = {};
476
+ const required = new Set();
477
+ for (const part of parts) {
478
+ checkSchemaKeywords(part);
479
+ if ((part.type !== undefined && part.type !== 'object') || part.enum || part.nullable || part.items) {
480
+ throw Error('Only non-nullable object allOf composition is supported');
481
+ }
482
+ if (part.additionalProperties !== undefined && typeof part.additionalProperties !== 'boolean') {
483
+ throw Error('Schema-valued additionalProperties is not supported');
484
+ }
485
+ for (const [key, property] of Object.entries(part.properties ?? {})) {
486
+ if (Object.hasOwn(properties, key) && JSON.stringify(properties[key]) !== JSON.stringify(property)) {
487
+ throw Error(`Overlapping allOf property ${key} needs explicit configuration`);
488
+ }
489
+ Object.defineProperty(properties, key, { value: property, enumerable: true, configurable: true });
490
+ }
491
+ for (const key of part.required ?? [])
492
+ required.add(key);
493
+ }
494
+ const closed = parts.filter(part => part.additionalProperties === false);
495
+ if (closed.some(part => Object.keys(properties).some(key => !Object.hasOwn(part.properties ?? {}, key)))) {
496
+ throw Error('Extending a closed object with allOf is not supported');
497
+ }
498
+ return {
499
+ ...own,
500
+ type: 'object',
501
+ properties,
502
+ required: [...required],
503
+ ...(closed.length > 0 && { additionalProperties: false }),
504
+ // A read-only member still makes the composed property response-only.
505
+ ...(parts.some(part => part.readOnly === true) && { readOnly: true }),
506
+ };
507
+ }
508
+ /** Supported JSON subset, including nested objects/arrays. Unsupported shapes
509
+ * skip the operation with a warning rather than drafting a subtly wrong call. */
510
+ function openApiParam(raw, doc, depth = 0) {
511
+ if (depth > 12)
512
+ throw Error('OpenAPI schema is recursive or exceeds 12 levels');
513
+ const s = resolveOpenApiSchema(raw, doc, depth);
514
+ // Constraints such as minimum/pattern remain fail-closed until the shared
515
+ // runtime contract can enforce them; even an optional param must not silently
516
+ // discard validation rules from the source document.
517
+ checkSchemaKeywords(s);
518
+ const types = Array.isArray(s.type) ? s.type : [s.type ?? (s.properties ? 'object' : undefined)];
519
+ const concrete = types.filter(t => t !== 'null');
520
+ if (concrete.length !== 1 || !['string', 'number', 'integer', 'boolean', 'object', 'array'].includes(concrete[0] ?? ''))
521
+ throw Error('JSON schema must declare one supported type (optionally nullable)');
522
+ const t = concrete[0];
523
+ const param = { type: (t === 'integer' ? 'number' : t), ...(t === 'integer' && { integer: true }), ...(s.description && { description: s.description }), ...(s.enum && { choices: s.enum }), ...((s.nullable || types.includes('null')) && { nullable: true }) };
524
+ if (t === 'object') {
525
+ if (Object.hasOwn(s.properties ?? {}, '__proto__'))
526
+ throw Error('Object property __proto__ is not supported');
527
+ if (s.additionalProperties !== undefined && typeof s.additionalProperties !== 'boolean')
528
+ throw Error('Schema-valued additionalProperties is not supported');
529
+ if (s.additionalProperties !== undefined)
530
+ param.additionalProperties = s.additionalProperties;
531
+ param.properties = Object.fromEntries(Object.entries(s.properties ?? {})
532
+ .filter(([, value]) => value.readOnly !== true)
533
+ .map(([key, value]) => [key, resolveOpenApiSchema(value, doc, depth + 1)])
534
+ .filter(([, value]) => value.readOnly !== true)
535
+ .map(([key, value]) => [key, { ...openApiParam(value, doc, depth + 1), ...(s.required?.includes(key) && { required: true }) }]));
536
+ for (const key of s.required ?? [])
537
+ if (!Object.hasOwn(s.properties ?? {}, key))
538
+ throw Error(`Required property ${key} has no schema`);
539
+ }
540
+ if (t === 'array') {
541
+ if (!s.items)
542
+ throw Error('Array schema needs items');
543
+ param.items = openApiParam(s.items, doc, depth + 1);
544
+ }
545
+ return param;
546
+ }
547
+ /** Headers are case-insensitive, while query parameter names are not. */
548
+ function securityDestination(scheme) {
549
+ if (scheme.type === 'apiKey' && scheme.in === 'query')
550
+ return `query:${scheme.name}`;
551
+ return `header:${(scheme.type === 'apiKey' ? scheme.name : 'Authorization').toLowerCase()}`;
552
+ }
553
+ function supportedSecurityScheme(scheme) {
554
+ return !!scheme && (scheme.type === 'basic' ||
555
+ (scheme.type === 'http' && ['bearer', 'basic'].includes(scheme.scheme ?? '')) ||
556
+ (scheme.type === 'apiKey' && ['header', 'query'].includes(scheme.in ?? '') && !!scheme.name));
557
+ }
558
+ function selectSecurity(security, schemes, options, parameters) {
559
+ const headerCredentials = new Set();
560
+ for (const p of parameters) {
561
+ if (p.in === 'header' && p.name && CREDENTIAL_HEADERS.has(p.name.toLowerCase())) {
562
+ headerCredentials.add(`header:${p.name.toLowerCase()}`);
563
+ }
564
+ }
565
+ let reason = 'No supported security alternative; cookie auth, OAuth acquisition and refresh require explicit configuration';
566
+ // Public operations can still declare credential headers as parameters.
567
+ for (const [index, requirement] of (security.length ? security : [{}]).entries()) {
568
+ const keys = Object.keys(requirement);
569
+ if (!keys.every(key => supportedSecurityScheme(schemes[key])))
570
+ continue;
571
+ if (keys.length > 1 && options.secretName) {
572
+ reason = 'One secretName cannot represent an AND requirement with multiple credentials';
573
+ continue;
574
+ }
575
+ const destinations = keys.map(key => securityDestination(schemes[key]));
576
+ if (new Set(destinations).size !== destinations.length) {
577
+ reason = 'Security requirements conflict on the same header or query parameter';
578
+ continue;
579
+ }
580
+ // Check parameter credentials against each alternative before selecting it.
581
+ if (options.secretName && new Set([...headerCredentials, ...destinations]).size > 1) {
582
+ reason = 'One secretName cannot represent multiple credentials in one operation';
583
+ continue;
584
+ }
585
+ return security.length ? index : -1;
586
+ }
587
+ throw Error(reason);
588
+ }
589
+ /** A document can give different schemes names that normalize identically.
590
+ * Allocate per identity and reserve suffix space before truncating. */
591
+ function openApiSecretNames(schemes, options) {
592
+ const names = new Map();
593
+ const used = new Set();
594
+ const allocate = (identity, wanted) => {
595
+ const existing = names.get(identity);
596
+ if (existing)
597
+ return existing;
598
+ const normalized = normalizeSecretName(options.secretName || wanted);
599
+ let name = normalized;
600
+ if (!options.secretName) {
601
+ for (let index = 2; used.has(name); index++) {
602
+ const suffix = `_${index}`;
603
+ name = normalized.slice(0, 64 - suffix.length) + suffix;
604
+ }
605
+ }
606
+ names.set(identity, name);
607
+ used.add(name);
608
+ return name;
609
+ };
610
+ const defaults = Object.entries(schemes).filter(([, scheme]) => supportedSecurityScheme(scheme))
611
+ .map(([key, scheme]) => [key, secretNameFor(scheme.type === 'apiKey' ? scheme.name : 'Authorization', {})]);
612
+ for (const [key, wanted] of defaults) {
613
+ allocate(`scheme:${key}`, defaults.filter(([, name]) => name === wanted).length > 1 ? key : wanted);
614
+ }
615
+ return allocate;
616
+ }
617
+ /** Draft supported OpenAPI/Swagger requests; authentication follows each
618
+ * operation's security requirement, including AND, alternatives and security:[]. */
447
619
  export function fromOpenApi(doc, options = {}) {
448
620
  const draft = new Draft();
449
- // OpenAPI 3 `servers`, or Swagger 2's three separate fields.
450
- const base = doc.servers?.[0]?.url ??
451
- (doc.host ? `${doc.schemes?.[0] ?? 'https'}://${doc.host}${doc.basePath ?? ''}` : '');
452
- if (!base) {
453
- draft.warnings.push({
454
- kind: 'undefined-variable',
455
- message: 'The document names no server, so every url is a path with no host. Set one, or edit ' +
456
- 'the urls before creating the agent: the API refuses a url that is not absolute.',
457
- });
458
- }
621
+ const base = doc.servers?.[0]?.url ?? (doc.host ? `${doc.schemes?.[0] ?? 'https'}://${doc.host}${doc.basePath ?? ''}` : '');
622
+ if (!base)
623
+ draft.warnings.push({ kind: 'undefined-variable', message: 'The document names no server. Set absolute HTTP(S) tool URLs before creating the agent.' });
459
624
  const schemes = doc.components?.securitySchemes ?? doc.securityDefinitions ?? {};
460
- const firstScheme = Object.values(schemes)[0];
461
- for (const [path, operations] of Object.entries(doc.paths ?? {})) {
625
+ const secretName = openApiSecretNames(schemes, options);
626
+ for (const [path, item] of Object.entries(doc.paths ?? {}))
462
627
  for (const method of OPENAPI_METHODS) {
463
- const op = operations[method];
628
+ const op = item[method];
464
629
  if (!op)
465
630
  continue;
466
- const candidateName = toolNameFrom(op.operationId ?? `${method}_${path}`, 'op');
467
- const fullUrl = `${base}${path}`;
468
- if (options.include && !options.include({ name: candidateName, method: method.toUpperCase(), url: fullUrl })) {
631
+ const candidate = toolNameFrom(op.operationId ?? `${method}_${path}`, 'op');
632
+ if (options.include && !options.include({ name: candidate, method: method.toUpperCase(), url: base + path }))
469
633
  continue;
470
- }
471
- const name = draft.uniqueName(candidateName);
472
- const params = {};
473
- const headerPairs = {};
474
- // A path template's `{order-id}` is a wire name like any other, so it
475
- // gets the same normalisation as a query key. Declared or not: a brace
476
- // in the path has to resolve, and an operation that never declared the
477
- // parameter would otherwise leave `{{params.order-id}}` in the url.
478
- let url = `${base}${path}`.replace(/\{([^}]+)\}/g, (_m, p) => `{{params.${draft.param(params, p, { type: 'string', required: true }, name)}}}`);
479
- const queryPairs = [];
480
- for (const p of op.parameters ?? []) {
481
- if (!p.name)
482
- continue;
483
- const def = {
484
- type: openApiType(p.schema?.type),
485
- ...(p.description ? { description: p.description } : {}),
486
- ...(p.required ? { required: true } : {}),
487
- ...(p.schema?.enum ? { choices: p.schema.enum } : {}),
488
- };
489
- // A CREDENTIAL HEADER IS NEVER A PARAM. The model would have to be
490
- // handed the token to fill it in, and the whole point of the secret
491
- // placeholder is that it never sees one.
492
- if (p.in === 'header' && CREDENTIAL_HEADERS.has(p.name.toLowerCase())) {
493
- const secret = secretNameFor(p.name, options);
494
- headerPairs[p.name] = `{{secrets.${secret}}}`;
495
- draft.secret(secret, name, `securityScheme (header parameter "${p.name}")`);
496
- continue;
634
+ const name = draft.uniqueName(candidate);
635
+ try {
636
+ const merged = new Map();
637
+ for (const raw of [...(item.parameters ?? []), ...(op.parameters ?? [])]) {
638
+ const p = dereference(raw, doc);
639
+ merged.set(p.in + ':' + p.name, p);
497
640
  }
498
- // A cookie parameter cannot be expressed: it would have to be merged
499
- // into a single Cookie header with every other one. Say so rather
500
- // than declaring a param that goes nowhere.
501
- if (p.in === 'cookie') {
502
- draft.warnings.push({
503
- kind: 'unsupported-body',
504
- tool: name,
505
- message: `Cookie parameter "${p.name}" is not supported; the tool was created without it.`,
506
- });
507
- continue;
641
+ const security = op.security ?? doc.security ?? [];
642
+ const selected = selectSecurity(security, schemes, options, merged.values());
643
+ const keys = Object.keys(security[selected] ?? {});
644
+ const credentialDestinations = new Set(keys.map(key => securityDestination(schemes[key])));
645
+ const params = {}, headers = {}, query = [];
646
+ let url = (base + path).replace(/\{([^}]+)\}/g, (_match, p) => `{{paramsEncoded.${draft.param(params, p, { type: 'string', required: true }, name, 'path:' + p)}}}`);
647
+ let body;
648
+ for (const p of merged.values()) {
649
+ if (!p.name)
650
+ throw Error('Parameter has no name');
651
+ // A declared auth parameter and a security scheme describe the same
652
+ // wire credential. Emit it once, as a secret, never as model input.
653
+ const destination = `${p.in}:${p.in === 'header' ? p.name.toLowerCase() : p.name}`;
654
+ if (credentialDestinations.has(destination))
655
+ continue;
656
+ if (p.in === 'cookie') {
657
+ if (p.required)
658
+ throw Error('Required cookie parameters are not supported');
659
+ draft.warnings.push({ kind: 'unsupported-body', tool: name, message: `Optional cookie parameter "${p.name}" is not supported; it was omitted.` });
660
+ continue;
661
+ }
662
+ if (!['path', 'query', 'header', 'body'].includes(p.in ?? ''))
663
+ throw Error(`Unsupported parameter location ${p.in}`);
664
+ const schema = p.schema ?? { type: p.type ?? 'string', ...(p.enum && { enum: p.enum }) };
665
+ const def = openApiParam(schema, doc);
666
+ if (p.description)
667
+ def.description = p.description;
668
+ if (p.required || p.in === 'path')
669
+ def.required = true;
670
+ if (p.in !== 'body' && (['object', 'array'].includes(def.type) || (p.style && !['simple', 'form'].includes(p.style))))
671
+ throw Error('Structured parameter serialization is unsupported outside a JSON body');
672
+ if (p.in === 'header' && CREDENTIAL_HEADERS.has(p.name.toLowerCase())) {
673
+ const secret = secretName(`header:${p.name.toLowerCase()}`, secretNameFor(p.name, {}));
674
+ headers[p.name] = `{{secrets.${secret}}}`;
675
+ draft.secret(secret, name, `header parameter ${p.name}`);
676
+ continue;
677
+ }
678
+ const param = draft.param(params, p.name, def, name, p.in + ':' + p.name);
679
+ if (p.in === 'query')
680
+ query.push(`${encodeURIComponent(p.name)}={{paramsEncoded.${param}}}`);
681
+ if (p.in === 'header')
682
+ headers[p.name] = `{{params.${param}}}`;
683
+ if (p.in === 'body')
684
+ body = `{{paramsJson.${param}}}`;
508
685
  }
509
- const param = draft.param(params, p.name, def, name);
510
- if (p.in === 'query') {
511
- queryPairs.push(`${encodeURIComponent(p.name)}={{paramsEncoded.${param}}}`);
686
+ if (op.requestBody && method !== 'get') {
687
+ const requestBody = dereference(op.requestBody, doc);
688
+ const media = Object.entries(requestBody.content ?? {}).find(([type]) => /(?:\/json|\+json)$/.test(type));
689
+ if (!media?.[1].schema)
690
+ throw Error('Only a JSON request body with a declared schema is supported');
691
+ const def = openApiParam(media[1].schema, doc);
692
+ if (requestBody.required)
693
+ def.required = true;
694
+ const param = draft.param(params, 'body', def, name, 'requestBody');
695
+ body = `{{paramsJson.${param}}}`;
696
+ headers['Content-Type'] = media[0];
512
697
  }
513
- else if (p.in === 'header') {
514
- // DECLARED AND NEVER SENT is the failure this closes. Every
515
- // parameter used to land in `params` whatever its `in` was, but
516
- // only query and path were wired to anything, so a required
517
- // `X-Tenant-Id` made the model supply a value that was dropped on
518
- // the floor and the request went out without the header.
519
- headerPairs[p.name] = `{{params.${param}}}`;
698
+ if (security.length > 1)
699
+ draft.warnings.push({ kind: 'security-choice', tool: name, message: `Selected security alternative ${selected + 1}; review the choice before creating the agent.` });
700
+ for (const key of keys) {
701
+ const scheme = schemes[key];
702
+ const header = scheme.type === 'apiKey' ? scheme.name : 'Authorization';
703
+ const secret = secretName(`scheme:${key}`, header);
704
+ if (scheme.type === 'apiKey' && scheme.in === 'query')
705
+ query.push(`${encodeURIComponent(header)}={{secretsEncoded.${secret}}}`);
706
+ else {
707
+ headers[header] = scheme.type === 'apiKey' ? `{{secrets.${secret}}}` : `${scheme.type === 'basic' || scheme.scheme === 'basic' ? 'Basic' : 'Bearer'} {{secrets.${secret}}}`;
708
+ }
709
+ draft.secret(secret, name, `securityScheme ${key}`);
520
710
  }
711
+ if (query.length)
712
+ url += '?' + query.join('&');
713
+ draft.tools.push({ name, description: describe([op.description, op.summary], name, method.toUpperCase(), path, draft), method: method.toUpperCase(), url, ...(Object.keys(headers).length && { headers }), ...(Object.keys(params).length && { params }), ...(draft.secretsFor(name).length && { secrets: draft.secretsFor(name) }), ...(body !== undefined && method !== 'get' && { body }) });
521
714
  }
522
- if (queryPairs.length > 0)
523
- url = `${url}?${queryPairs.join('&')}`;
524
- if (firstScheme) {
525
- const secret = secretNameFor(firstScheme.in === 'header' && firstScheme.name ? firstScheme.name : 'authorization', options);
526
- const headerName = firstScheme.type === 'apiKey' && firstScheme.in === 'header' && firstScheme.name
527
- ? firstScheme.name
528
- : 'Authorization';
529
- headerPairs[headerName] =
530
- headerName.toLowerCase() === 'authorization'
531
- ? `${firstScheme.scheme === 'basic' ? 'Basic' : 'Bearer'} {{secrets.${secret}}}`
532
- : `{{secrets.${secret}}}`;
533
- draft.secret(secret, name, `securityScheme (${firstScheme.type ?? 'unknown'})`);
715
+ catch (error) {
716
+ draft.warnings.push({ kind: 'unsupported-operation', tool: name, message: `Skipped ${method.toUpperCase()} ${path}: ${error.message}. Configure this operation explicitly.` });
534
717
  }
535
- const headers = safeHeaders(headerPairs, name, draft, options, 'OpenAPI');
536
- const upper = method.toUpperCase();
537
- let body;
538
- if (op.requestBody && upper !== 'GET') {
539
- const types = Object.keys(op.requestBody.content ?? {});
540
- if (types.some((t) => t.includes('json'))) {
541
- // A LITERAL EXAMPLE IS NOT INVENTED HERE. The schema says what the
542
- // shape is; guessing values would produce a tool that posts
543
- // plausible nonsense. The agent fills this in, so it is left for
544
- // the developer to shape.
545
- body = '{{paramsJson.body}}';
546
- params.body = { type: 'string', description: 'JSON body for this request.', required: true };
547
- }
548
- else if (types.length > 0) {
549
- draft.warnings.push({
550
- kind: 'unsupported-body',
551
- tool: name,
552
- message: `Request body is ${types.join(', ')}, which is not supported; the tool was created without one.`,
553
- });
554
- }
555
- }
556
- draft.tools.push({
557
- name,
558
- description: describe([op.description, op.summary], name, upper, path, draft),
559
- method: upper,
560
- url,
561
- ...(Object.keys(headers).length > 0 && { headers }),
562
- ...(Object.keys(params).length > 0 && { params }),
563
- ...(draft.secretsFor(name).length > 0 && { secrets: draft.secretsFor(name) }),
564
- ...(body !== undefined && { body }),
565
- });
566
718
  }
567
- }
568
- return draft.finish(options);
719
+ const result = draft.finish(options);
720
+ const emitted = new Set(result.tools.map(t => t.name));
721
+ result.secrets = result.secrets.map(s => ({ ...s, usedBy: s.usedBy.filter(name => emitted.has(name)) })).filter(s => s.usedBy.length > 0);
722
+ return result;
569
723
  }
570
724
  /**
571
725
  * Fetch an OpenAPI document and draft tools from it.