@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.
- package/README.md +204 -23
- package/account.d.ts +154 -0
- package/account.js +219 -0
- package/account.js.map +1 -0
- package/agent.d.ts +217 -88
- package/agent.js +295 -125
- package/agent.js.map +1 -1
- package/apps.generated.d.ts +154 -0
- package/apps.generated.js +152 -0
- package/apps.generated.js.map +1 -0
- package/attach.d.ts +25 -26
- package/attach.js +53 -66
- package/attach.js.map +1 -1
- package/connection.d.ts +79 -82
- package/connection.js +67 -134
- package/connection.js.map +1 -1
- package/conversation.d.ts +55 -35
- package/conversation.js +70 -50
- package/conversation.js.map +1 -1
- package/database.d.ts +53 -0
- package/database.js +69 -0
- package/database.js.map +1 -0
- package/end-user.d.ts +58 -47
- package/end-user.js +62 -72
- package/end-user.js.map +1 -1
- package/errors.d.ts +2 -1
- package/errors.js +2 -1
- package/errors.js.map +1 -1
- package/http.d.ts +5 -0
- package/http.js +109 -8
- package/http.js.map +1 -1
- package/import-tools.d.ts +48 -33
- package/import-tools.js +281 -127
- package/import-tools.js.map +1 -1
- package/index.d.ts +5 -2
- package/index.js +9 -2
- package/index.js.map +1 -1
- package/load-agent-dir.js +28 -5
- package/load-agent-dir.js.map +1 -1
- package/package.json +5 -1
- package/secret.d.ts +1 -1
- package/secret.js +1 -1
- package/secret.js.map +1 -1
- package/templates-inline.js +1 -1
- package/templates-inline.js.map +1 -1
- package/turn.d.ts +1 -1
- package/turn.js +77 -29
- package/turn.js.map +1 -1
- package/types.d.ts +409 -83
- package/types.js.map +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.js.map +1 -1
- package/workflows.d.ts +10 -0
- package/workflows.js +16 -0
- package/workflows.js.map +1 -1
- package/workspace.d.ts +80 -62
- package/workspace.js +92 -75
- package/workspace.js.map +1 -1
- package/connection-status.d.ts +0 -28
- package/connection-status.js +0 -61
- 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${
|
|
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
|
|
430
|
-
if (
|
|
431
|
-
return
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
(
|
|
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
|
|
461
|
-
for (const [path,
|
|
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 =
|
|
628
|
+
const op = item[method];
|
|
464
629
|
if (!op)
|
|
465
630
|
continue;
|
|
466
|
-
const
|
|
467
|
-
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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
|
-
|
|
523
|
-
|
|
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
|
-
|
|
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.
|