apps 0.0.1-beta.25 → 0.0.1-beta.26

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/js/openapi.js CHANGED
@@ -8,20 +8,25 @@ import {
8
8
  import {
9
9
  protocolOperations,
10
10
  protocolRouter
11
- } from "./chunk-K3MQX2KO.js";
11
+ } from "./chunk-OI7OI2U3.js";
12
12
  import {
13
+ OpenapiCompileError,
14
+ OpenapiCompileErrorCode,
13
15
  OpenapiError,
14
16
  OpenapiErrorResponse,
15
17
  OpenapiOperation,
16
18
  OpenapiParameter,
17
19
  OpenapiRequestBody,
20
+ OpenapiSkippedOperation,
18
21
  OpenapiToolsOptions,
19
22
  defaultOpenapiResponseLimits,
20
23
  isOpenapiFileSchema,
24
+ isOpenapiJsonSequence,
25
+ isOpenapiReadMethod,
21
26
  isOpenapiTextMedia,
22
27
  openapiBinaryResultSchema,
23
28
  openapiMediaKind
24
- } from "./chunk-J3RUZMLO.js";
29
+ } from "./chunk-EXDR4HVM.js";
25
30
  import "./chunk-PC5RHPUP.js";
26
31
  import {
27
32
  fromPromise,
@@ -68,9 +73,11 @@ import {
68
73
  Duration_exports,
69
74
  Effect_exports,
70
75
  Encoding_exports,
76
+ Exit_exports,
71
77
  JsonPointer_exports,
72
78
  JsonSchema_exports,
73
79
  Option_exports,
80
+ SchemaIssue_exports,
74
81
  Schema_exports,
75
82
  Stream_exports
76
83
  } from "./chunk-DM5U7G7V.js";
@@ -189,6 +196,18 @@ function operationPath(op) {
189
196
  return new RegExp("^" + pattern + "$");
190
197
  }
191
198
  var bytes = (value) => Uint8Array.from(atob(scalar(value)), (char) => char.charCodeAt(0));
199
+ function querystringValue(parameter, value) {
200
+ const [media] = Object.keys(parameter.content ?? {});
201
+ if (media === void 0) throw new Error("A querystring parameter declares no media type");
202
+ const kind = openapiMediaKind(media);
203
+ if (kind === "form") {
204
+ const form = new URLSearchParams();
205
+ for (const [name, field] of Object.entries(object(value)))
206
+ for (const item of Array.isArray(field) ? field : [field]) form.append(name, scalar(item));
207
+ return form.toString();
208
+ }
209
+ return encodeURIComponent(kind === "json" ? JSON.stringify(value) : scalar(value));
210
+ }
192
211
  function createRequest(config) {
193
212
  const { methods, oauth } = config;
194
213
  function selectedCredentials(op, account) {
@@ -229,6 +248,7 @@ function createRequest(config) {
229
248
  throw new Error("The selected account cannot call this tool");
230
249
  const parameters = {};
231
250
  let path = op.path;
251
+ const querystrings = [];
232
252
  const swaggerParameters = op.request.parameters.filter((p) => {
233
253
  const group = args[p.in === "header" ? "headers" : p.in];
234
254
  const value = group === void 0 ? void 0 : object(group)[p.name];
@@ -237,6 +257,12 @@ function createRequest(config) {
237
257
  path = path.replaceAll(`{${p.name}}`, reservedPathValue(value));
238
258
  return false;
239
259
  }
260
+ if (p.in === "querystring") {
261
+ if (value !== void 0) querystrings.push(querystringValue(p, value));
262
+ else if (p.required === true)
263
+ throw new Error("A required querystring parameter is missing");
264
+ return false;
265
+ }
240
266
  if (group !== void 0) parameters[`${p.in}.${p.name}`] = value;
241
267
  return true;
242
268
  });
@@ -288,6 +314,8 @@ function createRequest(config) {
288
314
  if (pathname.split("/").some((segment) => /^(?:\.|%2e){1,2}$/i.test(segment)))
289
315
  throw new Error("This path value would change the request path");
290
316
  const url = new URL(prepared2.url);
317
+ if (querystrings.length > 0)
318
+ url.search = [url.search.slice(1), ...querystrings].filter((part) => part !== "").join("&");
291
319
  const origin = new URL(op.baseUrl).origin;
292
320
  if (url.origin !== origin || !operationPath(op).test(url.pathname) || url.username || url.password)
293
321
  throw new Error("The request escaped its API operation");
@@ -337,7 +365,7 @@ function createRequest(config) {
337
365
  if (!isOpenapiTextMedia(contentType))
338
366
  return { base64: Encoding_exports.encodeBase64(data), contentType };
339
367
  const text = new TextDecoder().decode(data);
340
- return contentType.includes("json") && !contentType.includes("ndjson") ? yield* Schema_exports.decodeUnknownEffect(Schema_exports.fromJsonString(Schema_exports.Json))(text) : text;
368
+ return contentType.includes("json") && !isOpenapiJsonSequence(contentType) ? yield* Schema_exports.decodeUnknownEffect(Schema_exports.fromJsonString(Schema_exports.Json))(text) : text;
341
369
  })
342
370
  ).pipe(
343
371
  Effect_exports.withSpan("provider.openapi.call", {
@@ -477,7 +505,7 @@ var openapiToolsEffect = (options, prepared) => Effect_exports.gen(function* ()
477
505
  const { input, errors } = schemas;
478
506
  const tool = {
479
507
  description: op.description,
480
- readOnly: ["GET", "HEAD", "OPTIONS"].includes(op.method),
508
+ readOnly: isOpenapiReadMethod(op.method),
481
509
  input,
482
510
  run: (_context, value) => Schema_exports.decodeUnknownEffect(input)(
483
511
  fillParameterDefaults(op, config.parameterDefaults, value)
@@ -505,43 +533,6 @@ var openapiToolsEffect = (options, prepared) => Effect_exports.gen(function* ()
505
533
  return Object.fromEntries(entries);
506
534
  });
507
535
 
508
- // src/contracts/openapi-compile.ts
509
- var OpenapiCompileErrorCode = Schema_exports.Literals([
510
- "server_protocol",
511
- "server_url",
512
- "external_reference",
513
- "circular_reference",
514
- "schema_keyword",
515
- "schema_reference",
516
- "missing_component",
517
- "openapi_version",
518
- "auth_helper",
519
- "auth_missing",
520
- "operation_path",
521
- "duplicate_operation",
522
- "server_missing",
523
- "multiple_hosts",
524
- "parameter_encoding",
525
- "parameter_style",
526
- "request_body",
527
- "combined_oauth",
528
- "auth_method",
529
- "input_schema",
530
- "no_operations",
531
- "no_supported_operations",
532
- "invalid_document",
533
- "source_generation",
534
- "authoring_reference"
535
- ]);
536
- var OpenapiCompileError = class extends Schema_exports.TaggedError()(
537
- "OpenapiCompileError",
538
- {
539
- code: OpenapiCompileErrorCode,
540
- reason: Schema_exports.String
541
- }
542
- ) {
543
- };
544
-
545
536
  // ../../node_modules/.bun/js-yaml@5.4.2/node_modules/js-yaml/dist/js-yaml.mjs
546
537
  var NOT_RESOLVED = /* @__PURE__ */ Symbol("NOT_RESOLVED");
547
538
  function defineScalarTag(tagName, options) {
@@ -3908,7 +3899,8 @@ var yieldToRuntime = () => new Promise((resolve) => setTimeout(resolve, 0));
3908
3899
 
3909
3900
  // src/implementation/openapi-document.ts
3910
3901
  var DocumentObject = Schema_exports.declare(
3911
- (value) => typeof value === "object" && value !== null && !Array.isArray(value)
3902
+ (value) => typeof value === "object" && value !== null && !Array.isArray(value),
3903
+ { expected: "object" }
3912
3904
  );
3913
3905
  var documentObject = Schema_exports.decodeUnknownSync(DocumentObject);
3914
3906
  var record = documentObject;
@@ -3916,6 +3908,79 @@ function fail(code, reason) {
3916
3908
  throw new OpenapiCompileError({ code, reason });
3917
3909
  }
3918
3910
  var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3911
+ var pointerToken = (key) => key.replaceAll("~", "~0").replaceAll("/", "~1");
3912
+ var issueFormat = SchemaIssue_exports.makeFormatterStandardSchemaV1({
3913
+ leafHook: (issue) => SchemaIssue_exports.hasInput(issue) ? "Invalid value" : SchemaIssue_exports.defaultLeafHook(issue),
3914
+ checkHook: (issue) => SchemaIssue_exports.hasInput(issue) || SchemaIssue_exports.hasInput(issue.issue) ? SchemaIssue_exports.defaultCheckHook(issue) ?? "Invalid value" : SchemaIssue_exports.defaultCheckHook(issue)
3915
+ });
3916
+ var decodeDefinition = (schema2, value, at, what) => {
3917
+ try {
3918
+ return Schema_exports.decodeUnknownSync(schema2)(value);
3919
+ } catch (error) {
3920
+ if (!Schema_exports.isSchemaError(error)) throw error;
3921
+ const [issue] = issueFormat(error.issue).issues;
3922
+ const path = (issue?.path ?? []).map(
3923
+ (key) => pointerToken(String(typeof key === "object" ? key.key : key))
3924
+ );
3925
+ return fail(
3926
+ "invalid_document",
3927
+ `The ${what} at ${[at, ...path].join("/")} is invalid: ${issue?.message ?? "it does not match OpenAPI"}.`
3928
+ );
3929
+ }
3930
+ };
3931
+ var schemaKeywords = /* @__PURE__ */ new Set([
3932
+ "items",
3933
+ "additionalProperties",
3934
+ "not",
3935
+ "if",
3936
+ "then",
3937
+ "else",
3938
+ "contains",
3939
+ "propertyNames",
3940
+ "unevaluatedItems",
3941
+ "unevaluatedProperties",
3942
+ "contentSchema"
3943
+ ]);
3944
+ var schemaArrayKeywords = /* @__PURE__ */ new Set(["allOf", "anyOf", "oneOf", "prefixItems"]);
3945
+ var schemaMapKeywords = /* @__PURE__ */ new Set(["properties", "patternProperties", "$defs", "dependentSchemas"]);
3946
+ var isSchemaValue = (value) => typeof value === "boolean" || isObject2(value);
3947
+ var jsonKind = (value) => Array.isArray(value) ? "an array" : value === null ? "null" : typeof value === "object" ? "an object" : `a ${typeof value}`;
3948
+ var holdsReference = (value) => Array.isArray(value) ? value.some(holdsReference) : isObject2(value) && (typeof value.$ref === "string" || Object.values(value).some(holdsReference));
3949
+ var checkSchemaKeywords = (schema2, at) => {
3950
+ const path = [];
3951
+ const child = (value, problem) => {
3952
+ if (isSchemaValue(value)) return visit(value);
3953
+ if (holdsReference(value))
3954
+ fail(
3955
+ "schema_keyword",
3956
+ `The schema at ${[at, ...path].join("/")} is invalid: ${problem()}, so the reference inside it cannot be resolved.`
3957
+ );
3958
+ };
3959
+ const visit = (node) => {
3960
+ if (!isObject2(node)) return;
3961
+ for (const [key, value] of Object.entries(node)) {
3962
+ path.push(pointerToken(key));
3963
+ if (schemaKeywords.has(key))
3964
+ child(value, () => `"${key}" must be a schema, not ${jsonKind(value)}`);
3965
+ else if (schemaArrayKeywords.has(key) || schemaMapKeywords.has(key)) {
3966
+ const entries = schemaArrayKeywords.has(key) ? Array.isArray(value) ? [...value.entries()] : void 0 : isObject2(value) ? Object.entries(value) : void 0;
3967
+ if (entries === void 0)
3968
+ child(
3969
+ value,
3970
+ () => schemaArrayKeywords.has(key) ? `"${key}" must be an array of schemas, not ${jsonKind(value)}` : `"${key}" must map names to schemas, not ${jsonKind(value)}`
3971
+ );
3972
+ else
3973
+ for (const [name, item] of entries) {
3974
+ path.push(pointerToken(String(name)));
3975
+ child(item, () => `each "${key}" entry must be a schema, not ${jsonKind(item)}`);
3976
+ path.pop();
3977
+ }
3978
+ }
3979
+ path.pop();
3980
+ }
3981
+ };
3982
+ visit(schema2);
3983
+ };
3919
3984
  var pathItemMaps = /* @__PURE__ */ new Set(["paths", "webhooks", "pathItems"]);
3920
3985
  var localPath = (ref) => {
3921
3986
  if (!ref.startsWith("#")) return void 0;
@@ -3931,6 +3996,7 @@ var localPath = (ref) => {
3931
3996
  };
3932
3997
  function dereference(scope) {
3933
3998
  const active = /* @__PURE__ */ new Set();
3999
+ const origins = /* @__PURE__ */ new WeakMap();
3934
4000
  const target = (ref) => {
3935
4001
  const path = localPath(ref);
3936
4002
  let node = scope;
@@ -3964,14 +4030,15 @@ function dereference(scope) {
3964
4030
  active.delete(ref);
3965
4031
  if (!isObject2(resolved2)) return resolved2;
3966
4032
  const { $ref: _, ...siblings } = value;
3967
- if (pathItem) return { ...resolved2, ...fields(siblings) };
3968
- return {
4033
+ const result = pathItem ? { ...resolved2, ...fields(siblings) } : {
3969
4034
  ...resolved2,
3970
4035
  ...typeof siblings.summary === "string" ? { summary: siblings.summary } : {},
3971
4036
  ...typeof siblings.description === "string" ? { description: siblings.description } : {}
3972
4037
  };
4038
+ origins.set(result, ref);
4039
+ return result;
3973
4040
  };
3974
- return record(visit(scope, false));
4041
+ return { document: record(visit(scope, false)), origins };
3975
4042
  }
3976
4043
  var DocumentSpecification = Schema_exports.Struct({
3977
4044
  ...Specification.fields,
@@ -3983,29 +4050,99 @@ var DocumentSpecification = Schema_exports.Struct({
3983
4050
  })
3984
4051
  )
3985
4052
  });
4053
+ var MediaTypes = Schema_exports.Array(Schema_exports.String);
4054
+ var Swagger2Parameters = Schema_exports.Array(DocumentObject);
4055
+ var Swagger2Operation = Schema_exports.Struct({
4056
+ parameters: Schema_exports.optionalKey(Swagger2Parameters),
4057
+ consumes: Schema_exports.optionalKey(MediaTypes),
4058
+ produces: Schema_exports.optionalKey(MediaTypes),
4059
+ responses: Schema_exports.optionalKey(DocumentObject)
4060
+ });
4061
+ var Swagger2PathItem = Schema_exports.Struct({
4062
+ parameters: Schema_exports.optionalKey(Swagger2Parameters),
4063
+ get: Schema_exports.optionalKey(Swagger2Operation),
4064
+ put: Schema_exports.optionalKey(Swagger2Operation),
4065
+ post: Schema_exports.optionalKey(Swagger2Operation),
4066
+ delete: Schema_exports.optionalKey(Swagger2Operation),
4067
+ options: Schema_exports.optionalKey(Swagger2Operation),
4068
+ head: Schema_exports.optionalKey(Swagger2Operation),
4069
+ patch: Schema_exports.optionalKey(Swagger2Operation)
4070
+ });
4071
+ var Swagger2Definition = Schema_exports.Struct({
4072
+ paths: Schema_exports.optionalKey(DocumentObject),
4073
+ consumes: Schema_exports.optionalKey(MediaTypes),
4074
+ produces: Schema_exports.optionalKey(MediaTypes),
4075
+ securityDefinitions: Schema_exports.optionalKey(Schema_exports.Record(Schema_exports.String, DocumentObject))
4076
+ });
4077
+ var swagger2Methods = ["get", "put", "post", "delete", "options", "head", "patch"];
4078
+ function checkSwagger2(document) {
4079
+ const definition = decodeDefinition(Swagger2Definition, document, "#", "definition");
4080
+ for (const [path, value] of Object.entries(definition.paths ?? {})) {
4081
+ if (!path.startsWith("/")) continue;
4082
+ const itemAt = `#/paths/${pointerToken(path)}`;
4083
+ const item = decodeDefinition(Swagger2PathItem, value, itemAt, "path item");
4084
+ for (const method of swagger2Methods)
4085
+ for (const [status, response] of Object.entries(item[method]?.responses ?? {}))
4086
+ if (!status.startsWith("x-"))
4087
+ decodeDefinition(
4088
+ DocumentObject,
4089
+ response,
4090
+ `${itemAt}/${method}/responses/${pointerToken(status)}`,
4091
+ "response"
4092
+ );
4093
+ }
4094
+ }
3986
4095
  function upgradeOwned(document) {
3987
- return record(upgradeOpenApi30InPlace(record(upgradeFromTwoToThree(document))));
4096
+ if (typeof document.swagger === "string" && document.swagger.startsWith("2.0"))
4097
+ checkSwagger2(document);
4098
+ let upgraded;
4099
+ try {
4100
+ upgraded = upgradeFromTwoToThree(document);
4101
+ } catch (error) {
4102
+ if (!(error instanceof TypeError)) throw error;
4103
+ return fail(
4104
+ "invalid_document",
4105
+ "The Swagger 2.0 definition could not be converted to OpenAPI 3.0: part of it does not have the structure Swagger 2.0 requires, such as a null entry in a path item or response map."
4106
+ );
4107
+ }
4108
+ return record(upgradeOpenApi30InPlace(record(upgraded)));
3988
4109
  }
4110
+ var supportedVersion = /^3\.[12]\.\d+$/;
3989
4111
  async function openApiDocument(input) {
3990
4112
  const root = upgradeOwned(record(input));
3991
4113
  await yieldToRuntime();
3992
- const spec = Schema_exports.decodeUnknownSync(DocumentSpecification)(root);
3993
- if (!spec.openapi.startsWith("3.1."))
3994
- fail("openapi_version", "This importer supports Swagger 2.0 and OpenAPI 3.0 and 3.1.");
4114
+ const spec = decodeDefinition(DocumentSpecification, root, "#", "definition");
4115
+ if (!supportedVersion.test(spec.openapi))
4116
+ fail(
4117
+ "openapi_version",
4118
+ `This importer supports Swagger 2.0 and OpenAPI 3.0, 3.1 and 3.2, not openapi ${JSON.stringify(spec.openapi.slice(0, 32))}.`
4119
+ );
3995
4120
  const components = spec.components?.schemas ?? {};
3996
4121
  const convert = JsonSchema_exports.fromSchemaOpenApi3_1;
3997
4122
  const componentsObject = root.components === void 0 ? {} : record(root.components);
3998
- const restored = dereference({ ...root, components: { ...componentsObject, schemas: {} } });
3999
- await yieldToRuntime();
4000
- const parsed = Schema_exports.decodeUnknownSync(DocumentSpecification)({
4001
- ...restored,
4002
- components: { ...record(restored.components), schemas: components }
4123
+ const { document: restored, origins } = dereference({
4124
+ ...root,
4125
+ components: { ...componentsObject, schemas: {} }
4003
4126
  });
4127
+ await yieldToRuntime();
4128
+ const parsed = decodeDefinition(
4129
+ DocumentSpecification,
4130
+ { ...restored, components: { ...record(restored.components), schemas: components } },
4131
+ "#",
4132
+ "definition"
4133
+ );
4004
4134
  const definitions = /* @__PURE__ */ new Map();
4005
4135
  const references2 = /* @__PURE__ */ new Map();
4006
4136
  return {
4007
4137
  spec: parsed,
4008
4138
  definitions,
4139
+ /**
4140
+ * The JSON Pointer of the object a local reference resolved to, so a diagnostic can locate
4141
+ * a resolved Response, Parameter, Request Body or Path Item where the definition declares it.
4142
+ */
4143
+ origin(value) {
4144
+ return isObject2(value) ? origins.get(value) : void 0;
4145
+ },
4009
4146
  /** Inspect a component schema without expanding its children. OpenAPI object refs must already be resolved by Swagger. */
4010
4147
  resolve(value) {
4011
4148
  const visited = /* @__PURE__ */ new Set();
@@ -4013,53 +4150,88 @@ async function openApiDocument(input) {
4013
4150
  const ref = value.$ref;
4014
4151
  const path = typeof ref === "string" ? JsonPointer_exports.parseUriFragment(ref) : void 0;
4015
4152
  if (typeof ref !== "string" || path?.length !== 3 || path[0] !== "components" || path[1] !== "schemas" || path[2] === void 0)
4016
- fail("external_reference", "An OpenAPI object reference could not be resolved locally.");
4153
+ fail(
4154
+ "external_reference",
4155
+ `The reference ${JSON.stringify(ref)} does not resolve within the definition. Only local references to objects the definition declares are supported.`
4156
+ );
4017
4157
  if (visited.has(ref))
4018
- fail("circular_reference", "A circular schema alias cannot be imported.");
4158
+ fail("circular_reference", `The schema ${ref} is a circular alias of itself.`);
4019
4159
  visited.add(ref);
4020
- value = record(Object.hasOwn(components, path[2]) ? components[path[2]] : void 0);
4160
+ if (!Object.hasOwn(components, path[2]))
4161
+ fail(
4162
+ "missing_component",
4163
+ `The reference ${ref} names a schema the definition does not declare.`
4164
+ );
4165
+ value = record(components[path[2]]);
4021
4166
  }
4022
4167
  return value;
4023
4168
  },
4024
4169
  /** Compose a Draft 2020-12 schema around API Schema Objects. Only values passed to
4025
4170
  * `api` use the document's OpenAPI dialect; Executor-authored wrappers are already
4026
- * Draft 2020-12 and must not be reinterpreted. Reached components are converted once into
4027
- * the shared `definitions` and stay references here; nothing is inlined or copied per operation.
4171
+ * Draft 2020-12 and must not be reinterpreted. `api` takes the schema's JSON Pointer in the
4172
+ * definition, which failures name. Reached components are converted once into the shared
4173
+ * `definitions` and stay references here; nothing is inlined or copied per operation.
4028
4174
  */
4029
4175
  schema(build) {
4030
4176
  const added = [];
4031
4177
  const direct = /* @__PURE__ */ new Set();
4032
4178
  const local = {};
4033
- const visit = (input2, direct2) => {
4034
- const document = convert(input2, {
4035
- onReference(ref) {
4036
- const path = JsonPointer_exports.parseUriFragment(ref);
4037
- const name = path?.[2];
4038
- if (path?.length !== 3 || path[0] !== "components" || path[1] !== "schemas" || name === void 0)
4039
- fail("schema_reference", "Only local component schema references are supported.");
4040
- const component = Object.hasOwn(components, name) ? components[name] : void 0;
4041
- if (component === void 0)
4042
- fail("missing_component", "An API schema references a missing component.");
4043
- direct2.add(name);
4044
- if (!definitions.has(name)) {
4045
- added.push(name);
4046
- definitions.set(name, {});
4047
- const names = /* @__PURE__ */ new Set();
4048
- definitions.set(name, visit(component, names));
4049
- references2.set(name, names);
4179
+ const visit = (input2, direct2, at) => {
4180
+ checkSchemaKeywords(input2, at);
4181
+ let document;
4182
+ try {
4183
+ document = convert(input2, {
4184
+ onReference(ref) {
4185
+ const path = JsonPointer_exports.parseUriFragment(ref);
4186
+ const name = path?.[2];
4187
+ if (path?.length !== 3 || path[0] !== "components" || path[1] !== "schemas" || name === void 0)
4188
+ fail(
4189
+ "schema_reference",
4190
+ `The schema at ${at} references ${JSON.stringify(ref)}. Only references to #/components/schemas/<name> are supported.`
4191
+ );
4192
+ const component = Object.hasOwn(components, name) ? components[name] : void 0;
4193
+ if (component === void 0)
4194
+ fail(
4195
+ "missing_component",
4196
+ `The schema at ${at} references ${ref}, which the definition does not declare.`
4197
+ );
4198
+ direct2.add(name);
4199
+ if (!definitions.has(name)) {
4200
+ added.push(name);
4201
+ definitions.set(name, {});
4202
+ const names = /* @__PURE__ */ new Set();
4203
+ definitions.set(
4204
+ name,
4205
+ visit(component, names, `#/components/schemas/${pointerToken(name)}`)
4206
+ );
4207
+ references2.set(name, names);
4208
+ }
4050
4209
  }
4051
- }
4052
- });
4210
+ });
4211
+ } catch (error) {
4212
+ if (error instanceof OpenapiCompileError) throw error;
4213
+ return fail(
4214
+ "schema_keyword",
4215
+ `The schema at ${at} cannot be converted to JSON Schema 2020-12 without changing its constraints${error instanceof Error ? `: ${error.message}` : ""}.`
4216
+ );
4217
+ }
4053
4218
  return record({
4054
4219
  ...document.schema,
4055
4220
  ...Object.keys(document.definitions).length ? { $defs: document.definitions } : {}
4056
4221
  });
4057
4222
  };
4058
- const api = (input2) => {
4059
- const { $defs, ...converted } = visit(record(input2), direct);
4223
+ const api = (input2, at) => {
4224
+ const schema2 = input2 === true ? {} : input2 === false ? { not: {} } : isObject2(input2) ? input2 : fail(
4225
+ "schema_keyword",
4226
+ `The schema at ${at} must be a schema, not ${jsonKind(input2)}.`
4227
+ );
4228
+ const { $defs, ...converted } = visit(schema2, direct, at);
4060
4229
  for (const [name, definition] of Object.entries($defs === void 0 ? {} : record($defs))) {
4061
4230
  if (Object.hasOwn(local, name) && JSON.stringify(local[name]) !== JSON.stringify(definition))
4062
- fail("schema_reference", "An API schema has conflicting local definitions.");
4231
+ fail(
4232
+ "schema_reference",
4233
+ `The schema at ${at} declares the local definition ${JSON.stringify(name)} differently from another schema of the same operation.`
4234
+ );
4063
4235
  local[name] = definition;
4064
4236
  }
4065
4237
  return converted;
@@ -4074,10 +4246,11 @@ async function openApiDocument(input) {
4074
4246
  reached.add(name);
4075
4247
  pending.push(...references2.get(name) ?? []);
4076
4248
  }
4077
- if ([...reached].some((name) => Object.hasOwn(local, name)))
4249
+ const shared = [...reached].find((name) => Object.hasOwn(local, name));
4250
+ if (shared !== void 0)
4078
4251
  fail(
4079
4252
  "schema_reference",
4080
- "An API schema has conflicting local and component definitions."
4253
+ `A local definition named ${JSON.stringify(shared)} conflicts with the component schema of the same name.`
4081
4254
  );
4082
4255
  }
4083
4256
  return {
@@ -4090,11 +4263,7 @@ async function openApiDocument(input) {
4090
4263
  definitions.delete(name);
4091
4264
  references2.delete(name);
4092
4265
  }
4093
- if (error instanceof OpenapiCompileError) throw error;
4094
- return fail(
4095
- "schema_keyword",
4096
- "An API schema cannot be converted without changing its constraints."
4097
- );
4266
+ throw error;
4098
4267
  }
4099
4268
  }
4100
4269
  };
@@ -4130,6 +4299,15 @@ var leafOf = (input, group) => {
4130
4299
  const leaf = camel(seed);
4131
4300
  return leaf === "" || leaf === group ? fallbackLeaf(input, group) : leaf;
4132
4301
  };
4302
+ var pathTokens = (path) => segments(path).flatMap((segment) => {
4303
+ if (version.test(segment) || segment.toLowerCase() === "api") return [];
4304
+ const parameter = segment.startsWith("{") && segment.endsWith("}");
4305
+ const word = camel(parameter ? segment.slice(1, -1) : segment);
4306
+ return word === "" ? [] : [{ key: `${parameter ? "{" : ""}${word}`, word, parameter }];
4307
+ });
4308
+ var describePath = (tokens) => tokens.map(
4309
+ (token, index) => token.parameter ? `${tokens[index - 1]?.parameter === true ? "And" : "By"}${pascal(token.word)}` : pascal(token.word)
4310
+ ).join("");
4133
4311
  var hash = (input) => {
4134
4312
  const text = JSON.stringify([input.method.toUpperCase(), input.path, input.operationId ?? null]);
4135
4313
  let value = 0;
@@ -4142,18 +4320,30 @@ var planOperationNames = (inputs) => {
4142
4320
  const leaf = leafOf(input, group);
4143
4321
  const versionSegment = segments(input.path).map((segment) => segment.toLowerCase()).find((segment) => version.test(segment));
4144
4322
  const prefix = versionSegment === void 0 ? group : `${group}.${camel(versionSegment)}`;
4145
- return { input, group, leaf, prefix, versionSegment, name: `${group}.${leaf}` };
4323
+ const tokens = pathTokens(input.path);
4324
+ return { input, group, leaf, prefix, versionSegment, tokens, name: `${group}.${leaf}` };
4146
4325
  });
4147
4326
  const refine = (rename) => {
4148
4327
  const buckets = /* @__PURE__ */ new Map();
4149
4328
  for (const item of staged) buckets.set(item.name, [...buckets.get(item.name) ?? [], item]);
4150
4329
  for (const bucket of buckets.values())
4151
- if (bucket.length > 1) for (const item of bucket) item.name = rename(item);
4330
+ if (bucket.length > 1) {
4331
+ const names = bucket.map((item) => rename(item, bucket));
4332
+ for (const [index, item] of bucket.entries()) item.name = names[index] ?? item.name;
4333
+ }
4152
4334
  };
4153
4335
  refine((item) => item.versionSegment === void 0 ? item.name : `${item.prefix}.${item.leaf}`);
4336
+ refine((item, bucket) => {
4337
+ const shared = new Set(
4338
+ item.tokens.map((token) => token.key).filter((key) => bucket.every((other) => other.tokens.some((token) => token.key === key)))
4339
+ );
4340
+ const distinct = item.tokens.filter((token) => !shared.has(token.key));
4341
+ return distinct.length === 0 ? item.name : `${item.prefix}.${item.leaf}${describePath(distinct)}`;
4342
+ });
4343
+ refine((item) => `${item.prefix}.${item.leaf}${describePath(item.tokens)}`);
4154
4344
  refine((item) => `${item.prefix}.${item.leaf}${pascal(item.input.method)}`);
4155
4345
  refine((item) => `${item.prefix}.${item.leaf}${pascal(item.input.method)}${hash(item.input)}`);
4156
- return staged.map((item) => item.name);
4346
+ return staged.map((item) => ({ ...item.input, name: item.name }));
4157
4347
  };
4158
4348
 
4159
4349
  // src/implementation/openapi-compile.ts
@@ -4166,12 +4356,25 @@ var OperationNaming = Schema_exports.Struct({
4166
4356
  tags: Schema_exports.optionalKey(Schema_exports.Array(Schema_exports.String))
4167
4357
  });
4168
4358
  var identifier = (name) => name.replace(/[^a-zA-Z0-9_]/g, "_");
4359
+ function parseUrl(value, base) {
4360
+ try {
4361
+ return new URL(value, base);
4362
+ } catch {
4363
+ return fail2(
4364
+ "server_url",
4365
+ base === void 0 && !/^[a-z][a-z0-9+.-]*:/i.test(value) ? "The server URL is relative, and an inline definition has no URL to resolve it against. Set baseUrl." : "The server URL is not a valid URL. Set baseUrl."
4366
+ );
4367
+ }
4368
+ }
4169
4369
  function absolute(value) {
4170
- const url = new URL(value);
4370
+ const url = parseUrl(value);
4171
4371
  if (url.protocol !== "https:" && url.protocol !== "http:")
4172
- fail2("server_protocol", "Only HTTP APIs can be imported.");
4372
+ fail2("server_protocol", `The server uses ${url.protocol}; only HTTP APIs can be imported.`);
4173
4373
  if (url.username || url.password || url.hash || url.search || /[{}]/.test(value))
4174
- fail2("server_url", "This API needs a configured server URL before import.");
4374
+ fail2(
4375
+ "server_url",
4376
+ "The server URL has credentials, a query, a fragment or unresolved variables. Set baseUrl."
4377
+ );
4175
4378
  return url.href.replace(/\/$/, "");
4176
4379
  }
4177
4380
  function serverAddress(server, connectUrl, documentServer) {
@@ -4196,16 +4399,19 @@ function serverAddress(server, connectUrl, documentServer) {
4196
4399
  }
4197
4400
  const relative = new URL(template, "https://relative.invalid");
4198
4401
  const base = url?.protocol && url.host ? `${url.protocol.replace(/\W/g, "")}://${url.host}${url.pathname}` : url?.pathname ?? (template.startsWith("/") ? relative.pathname : relative.pathname.slice(1));
4199
- return absolute(new URL(`${base.endsWith("/") ? base.slice(0, -1) : base}/`, connectUrl).href);
4402
+ return absolute(parseUrl(`${base.endsWith("/") ? base.slice(0, -1) : base}/`, connectUrl).href);
4200
4403
  }
4201
4404
  var binaryInput = {
4202
4405
  type: "string",
4203
4406
  description: "File bytes encoded as base64.",
4204
4407
  contentEncoding: "base64"
4205
4408
  };
4206
- function responseSchema(document, operation, method) {
4409
+ var declaredAt = (document, value, inline) => document.origin(value) ?? inline;
4410
+ var mediaSchemaAt = (document, at, type, media) => `${declaredAt(document, media, `${at}/content/${pointerToken(type)}`)}/schema`;
4411
+ var responsesOf = (operation) => operation.responses === void 0 ? {} : record2(operation.responses);
4412
+ function responseSchema(document, operation, at, method) {
4207
4413
  if (method === "HEAD") return { type: "null" };
4208
- const success = Object.entries(operation.responses ?? {}).filter(
4414
+ const success = Object.entries(responsesOf(operation)).filter(
4209
4415
  ([status]) => /^2(?:[0-9]{2}|XX)$/i.test(status)
4210
4416
  );
4211
4417
  if (success.length === 0) return void 0;
@@ -4215,12 +4421,13 @@ function responseSchema(document, operation, method) {
4215
4421
  shapes.push(() => ({ type: "null" }));
4216
4422
  continue;
4217
4423
  }
4218
- const content = document.resolve(response).content;
4424
+ const responseAt = declaredAt(document, response, `${at}/responses/${pointerToken(status)}`);
4425
+ const content = document.resolve(record2(response)).content;
4219
4426
  if (content === void 0) return void 0;
4220
4427
  const media = Object.entries(record2(content));
4221
4428
  if (media.length === 0) return void 0;
4222
4429
  for (const [type, body] of media) {
4223
- if (!type.includes("json")) {
4430
+ if (!type.includes("json") || isOpenapiJsonSequence(type)) {
4224
4431
  shapes.push(
4225
4432
  () => isOpenapiTextMedia(type) ? { type: "string" } : openapiBinaryResultSchema
4226
4433
  );
@@ -4228,7 +4435,8 @@ function responseSchema(document, operation, method) {
4228
4435
  }
4229
4436
  const schema2 = record2(body).schema;
4230
4437
  if (schema2 === void 0) return void 0;
4231
- shapes.push((api) => api(schema2));
4438
+ const schemaAt = mediaSchemaAt(document, responseAt, type, body);
4439
+ shapes.push((api) => api(schema2, schemaAt));
4232
4440
  }
4233
4441
  }
4234
4442
  return document.schema((api) => ({
@@ -4251,14 +4459,14 @@ function errorContent(document, response) {
4251
4459
  return void 0;
4252
4460
  }
4253
4461
  }
4254
- function errorResponses(document, operation) {
4462
+ function errorResponses(document, operation, at) {
4255
4463
  const errors = [];
4256
4464
  const tagged = Schema_exports.Struct({
4257
4465
  description: Schema_exports.optionalKey(Schema_exports.String),
4258
4466
  required: Schema_exports.Array(Schema_exports.String),
4259
4467
  properties: Schema_exports.Record(Schema_exports.String, JsonObject)
4260
4468
  });
4261
- const visit = (status, input, visited = /* @__PURE__ */ new Set(), parents = []) => {
4469
+ const visit = (status, input, schemaAt, visited = /* @__PURE__ */ new Set(), parents = []) => {
4262
4470
  const object2 = Schema_exports.decodeUnknownOption(JsonObject)(input);
4263
4471
  if (Option_exports.isNone(object2)) return;
4264
4472
  const value = object2.value;
@@ -4266,9 +4474,14 @@ function errorResponses(document, operation) {
4266
4474
  if (typeof value.$ref === "string" && visited.has(value.$ref)) return;
4267
4475
  const next = typeof value.$ref === "string" ? /* @__PURE__ */ new Set([...visited, value.$ref]) : visited;
4268
4476
  const shape = document.resolve(value);
4477
+ const shapeAt = typeof value.$ref === "string" && value.$ref.startsWith("#/") ? value.$ref : schemaAt;
4269
4478
  const variants = shape.anyOf;
4270
4479
  if (Array.isArray(variants)) {
4271
- for (const variant of variants) visit(status, variant, next, [...parents, value]);
4480
+ for (const [index, variant] of variants.entries())
4481
+ visit(status, variant, `${shapeAt}/anyOf/${index}`, next, [
4482
+ ...parents,
4483
+ { schema: value, at: schemaAt }
4484
+ ]);
4272
4485
  return;
4273
4486
  }
4274
4487
  const parsed = Schema_exports.decodeUnknownOption(tagged)(shape);
@@ -4283,26 +4496,49 @@ function errorResponses(document, operation) {
4283
4496
  code,
4284
4497
  status,
4285
4498
  message: hasMessage ? { source: "body" } : { source: "schema", value: parsed.value.description },
4286
- schema: document.schema((api) => ({ allOf: [...parents, value].map(api) }))
4499
+ schema: document.schema((api) => ({
4500
+ allOf: [...parents, { schema: value, at: schemaAt }].map(
4501
+ (part) => api(part.schema, part.at)
4502
+ )
4503
+ }))
4287
4504
  });
4288
4505
  if (Option_exports.isSome(declaration)) errors.push(declaration.value);
4289
4506
  } catch (error) {
4290
4507
  if (!(error instanceof OpenapiCompileError) && !Schema_exports.isSchemaError(error)) throw error;
4291
4508
  }
4292
4509
  };
4293
- for (const [status, response] of Object.entries(operation.responses ?? {})) {
4510
+ for (const [status, response] of Object.entries(responsesOf(operation))) {
4294
4511
  if (!/^[45][0-9]{2}$/.test(status)) continue;
4295
- const content = errorContent(document, response);
4512
+ const content = errorContent(document, record2(response));
4296
4513
  if (content === void 0) continue;
4514
+ const responseAt = declaredAt(document, response, `${at}/responses/${pointerToken(status)}`);
4297
4515
  for (const [type, body] of Object.entries(record2(content))) {
4298
4516
  if (!type.split(";")[0]?.trim().match(/^application\/(?:[\w.-]+\+)?json$/i))
4299
4517
  continue;
4300
4518
  const schema2 = record2(body).schema;
4301
- if (schema2 !== void 0) visit(Number(status), schema2);
4519
+ if (schema2 !== void 0)
4520
+ visit(Number(status), schema2, mediaSchemaAt(document, responseAt, type, body));
4302
4521
  }
4303
4522
  }
4304
4523
  return errors;
4305
4524
  }
4525
+ var fixedMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
4526
+ var fixedMethods32 = [...fixedMethods, "QUERY"];
4527
+ var isImportedMethod = Schema_exports.is(OpenapiOperation.fields.method);
4528
+ var pathPrefixPattern = /^(?:\/(?:[^/?#{}]|\{[A-Za-z_][\w.-]*\})+)+$/;
4529
+ function prefixParameters(prefix) {
4530
+ if (!pathPrefixPattern.test(prefix))
4531
+ fail2(
4532
+ "operation_path",
4533
+ "pathPrefix must start with /, have no empty segment, trailing /, ? or #, and write each parameter as {name}."
4534
+ );
4535
+ const names = [...prefix.matchAll(/\{([^{}]+)\}/g)].flatMap(
4536
+ (match) => match[1] === void 0 ? [] : [match[1]]
4537
+ );
4538
+ if (new Set(names).size !== names.length)
4539
+ fail2("operation_path", "pathPrefix names the same parameter twice.");
4540
+ return names;
4541
+ }
4306
4542
  var compileOpenApiDocument = (entry, inputDocument, options = {}) => {
4307
4543
  let owned = inputDocument;
4308
4544
  const take = () => {
@@ -4335,7 +4571,7 @@ var compileOpenApiDocument = (entry, inputDocument, options = {}) => {
4335
4571
  { scheme: name, field: "password", part: "password", prefix: "" }
4336
4572
  ]);
4337
4573
  } else if (scheme.type === "oauth2") {
4338
- const code = record2(scheme.flows).authorizationCode;
4574
+ const code = scheme.flows === void 0 ? void 0 : record2(scheme.flows).authorizationCode;
4339
4575
  if (code !== void 0) {
4340
4576
  const flow = record2(code);
4341
4577
  if (typeof flow.authorizationUrl === "string" && typeof flow.tokenUrl === "string")
@@ -4343,9 +4579,9 @@ var compileOpenApiDocument = (entry, inputDocument, options = {}) => {
4343
4579
  name,
4344
4580
  config: entry.oauthDiscoveryUrl === void 0 ? {
4345
4581
  authorizationUrl: absolute(
4346
- new URL(flow.authorizationUrl, entry.connectUrl).href
4582
+ parseUrl(flow.authorizationUrl, entry.connectUrl).href
4347
4583
  ),
4348
- tokenUrl: absolute(new URL(flow.tokenUrl, entry.connectUrl).href),
4584
+ tokenUrl: absolute(parseUrl(flow.tokenUrl, entry.connectUrl).href),
4349
4585
  scopes: [
4350
4586
  ...entry.scopes ?? Object.keys(record2(flow.scopes ?? {}))
4351
4587
  ].sort()
@@ -4378,11 +4614,10 @@ var compileOpenApiDocument = (entry, inputDocument, options = {}) => {
4378
4614
  "This entry does not declare enough authentication details to generate an app."
4379
4615
  );
4380
4616
  const methods = /* @__PURE__ */ new Map();
4381
- const operations = [];
4382
- const skipped = /* @__PURE__ */ new Set();
4383
- const causes = /* @__PURE__ */ new Map();
4617
+ const skipped = [];
4384
4618
  const built = [];
4385
4619
  const documentServer = spec.servers?.[0];
4620
+ const prefix = options.pathPrefix === void 0 ? [] : prefixParameters(options.pathPrefix);
4386
4621
  const addresses = /* @__PURE__ */ new Map();
4387
4622
  const addressOf = (server) => {
4388
4623
  const key = JSON.stringify([server, documentServer?.variables ?? null]);
@@ -4398,64 +4633,128 @@ var compileOpenApiDocument = (entry, inputDocument, options = {}) => {
4398
4633
  if ("error" in known) throw known.error;
4399
4634
  return known.address;
4400
4635
  };
4636
+ const version32 = spec.openapi.startsWith("3.2.");
4401
4637
  const candidates = Object.entries(spec.paths).flatMap(([path, source]) => {
4402
4638
  const item = document.resolve(source);
4403
- return ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"].filter((method) => item[method.toLowerCase()] !== void 0).map((method) => ({ path, item, method }));
4639
+ const itemAt = declaredAt(document, source, `#/paths/${pointerToken(path)}`);
4640
+ const additional = version32 && item.additionalOperations !== void 0 ? Object.entries(record2(item.additionalOperations)).map(([method, value]) => ({
4641
+ method,
4642
+ value,
4643
+ at: `${itemAt}/additionalOperations/${pointerToken(method)}`
4644
+ })) : [];
4645
+ const fixed = (version32 ? fixedMethods32 : fixedMethods).filter((method) => item[method.toLowerCase()] !== void 0).map((method) => ({
4646
+ method,
4647
+ value: item[method.toLowerCase()],
4648
+ at: `${itemAt}/${method.toLowerCase()}`
4649
+ }));
4650
+ return [...fixed, ...additional].map((operation) => ({ ...operation, path, item, itemAt }));
4404
4651
  });
4405
4652
  if (!candidates.length) fail2("no_operations", "This API does not contain any operations.");
4406
- const names = planOperationNames(
4407
- candidates.map(({ path, item, method }) => {
4653
+ const named = planOperationNames(
4654
+ candidates.map((candidate) => {
4408
4655
  const naming = Schema_exports.decodeUnknownOption(OperationNaming)(
4409
- resolvedOrUndefined(document, item[method.toLowerCase()])
4656
+ resolvedOrUndefined(document, candidate.value)
4410
4657
  ).pipe(Option_exports.getOrUndefined);
4411
4658
  return {
4659
+ ...candidate,
4412
4660
  operationId: naming?.operationId,
4413
- tag: naming?.tags?.find((tag) => tag.trim() !== ""),
4414
- method,
4415
- path
4661
+ tag: naming?.tags?.find((tag) => tag.trim() !== "")
4416
4662
  };
4417
4663
  })
4418
4664
  );
4419
- for (const [index, { path, item, method }] of candidates.entries()) {
4665
+ const order = new Map(named.map(({ name }, index) => [name, index]));
4666
+ const inDefinitionOrder = (operations2) => [...operations2].sort((a, b) => (order.get(a.name) ?? 0) - (order.get(b.name) ?? 0));
4667
+ for (const [index, { name, path, item, itemAt, method, value, at }] of named.entries()) {
4420
4668
  if (index % 100 === 99) await yieldToRuntime();
4421
4669
  try {
4422
4670
  if (!path.startsWith("/") || path.includes("?") || path.includes("#"))
4423
- fail2("operation_path", "An operation has an invalid API path.");
4671
+ fail2("operation_path", "The path must start with / and contain no ? or #.");
4672
+ if (!isImportedMethod(method))
4673
+ fail2(
4674
+ "operation_method",
4675
+ `Executor does not send the HTTP method ${method}. It imports ${fixedMethods32.join(", ")} operations.`
4676
+ );
4424
4677
  const operationMethods = [];
4425
- const resolved2 = document.resolve(record2(item[method.toLowerCase()]));
4426
- const operation = Schema_exports.decodeUnknownSync(Operation)(resolved2);
4678
+ const resolved2 = document.resolve(record2(value));
4679
+ const operation = decodeDefinition(Operation, resolved2, at, "operation");
4427
4680
  const tags = Option_exports.getOrUndefined(
4428
4681
  Schema_exports.decodeUnknownOption(Schema_exports.Array(Schema_exports.NonEmptyString))(resolved2.tags)
4429
4682
  );
4430
- const name = names[index] ?? fail2("operation_path", "An operation has no name.");
4431
4683
  if (built.some((op) => op.operation.name === name))
4432
4684
  fail2(
4433
4685
  "duplicate_operation",
4434
- "The API has duplicate tool names. Update its operation IDs first."
4686
+ `Another operation has the same tool name, ${name}. Give one of them a distinct operationId.`
4435
4687
  );
4436
- const serverList = operation.servers ?? (Array.isArray(item.servers) ? Schema_exports.decodeUnknownSync(Specification.fields.servers)(item.servers) : void 0) ?? spec.servers;
4688
+ const serverList = operation.servers ?? (Array.isArray(item.servers) ? decodeDefinition(
4689
+ Specification.fields.servers,
4690
+ item.servers,
4691
+ `${itemAt}/servers`,
4692
+ "servers"
4693
+ ) : void 0) ?? spec.servers;
4437
4694
  const server = serverList?.[0];
4438
4695
  const serverUrl = options.baseUrl ?? server?.url;
4439
4696
  if (serverUrl === void 0)
4440
- fail2("server_missing", "The API has no server URL. Set an API base URL and try again.");
4697
+ fail2(
4698
+ "server_missing",
4699
+ "The definition declares no server for this operation. Set baseUrl."
4700
+ );
4441
4701
  const baseUrl = addressOf(
4442
4702
  options.baseUrl === void 0 && server !== void 0 ? server : { url: serverUrl }
4443
4703
  );
4444
- const combined = [
4445
- ...Array.isArray(item.parameters) ? item.parameters : [],
4446
- ...operation.parameters ?? []
4704
+ const declared = [
4705
+ ...(Array.isArray(item.parameters) ? item.parameters : []).map(
4706
+ (parameter, position) => ({
4707
+ parameter,
4708
+ at: declaredAt(document, parameter, `${itemAt}/parameters/${position}`)
4709
+ })
4710
+ ),
4711
+ ...(Array.isArray(resolved2.parameters) ? resolved2.parameters : []).map(
4712
+ (parameter, position) => ({
4713
+ parameter,
4714
+ at: declaredAt(document, parameter, `${at}/parameters/${position}`)
4715
+ })
4716
+ )
4447
4717
  ];
4448
4718
  const parameters = /* @__PURE__ */ new Map();
4449
- for (const parameter of combined) {
4450
- const p = Schema_exports.decodeUnknownSync(OpenapiParameter)(document.resolve(record2(parameter)));
4451
- parameters.set(`${p.in}:${p.name}`, p);
4719
+ for (const { parameter, at: parameterAt } of declared) {
4720
+ const p = decodeDefinition(
4721
+ OpenapiParameter,
4722
+ document.resolve(record2(parameter)),
4723
+ parameterAt,
4724
+ "parameter"
4725
+ );
4726
+ if (p.in === "querystring") {
4727
+ const [media, ...others] = Object.keys(p.content ?? {});
4728
+ const kind = media === void 0 || others.length > 0 ? void 0 : openapiMediaKind(media);
4729
+ if (kind !== "form" && kind !== "json" && kind !== "text")
4730
+ fail2(
4731
+ "parameter_encoding",
4732
+ `The querystring parameter at ${parameterAt} must declare one form, JSON or text media type in content.`
4733
+ );
4734
+ }
4735
+ parameters.set(`${p.in}:${p.name}`, { parameter: p, at: parameterAt });
4736
+ }
4737
+ for (const name2 of prefix) {
4738
+ const key = `path:${name2}`;
4739
+ if (path.includes(`{${name2}}`))
4740
+ fail2(
4741
+ "operation_path",
4742
+ `pathPrefix adds the path parameter {${name2}}, which this operation's path already uses.`
4743
+ );
4744
+ if (!parameters.has(key))
4745
+ parameters.set(key, {
4746
+ parameter: { name: name2, in: "path", required: true, schema: { type: "string" } },
4747
+ at: void 0
4748
+ });
4452
4749
  }
4453
4750
  const groups = /* @__PURE__ */ new Map();
4454
- for (const p of parameters.values()) {
4751
+ for (const { parameter: p, at: parameterAt } of parameters.values()) {
4455
4752
  const key = p.in === "header" ? "headers" : p.in;
4456
4753
  const group = groups.get(key) ?? { properties: {}, required: [] };
4457
- const content = p.content === void 0 ? void 0 : Object.values(p.content)[0];
4458
- group.properties[p.name] = p.schema ?? (content === void 0 ? {} : record2(content).schema ?? {});
4754
+ const [mediaType, content] = p.content === void 0 ? [] : Object.entries(p.content)[0] ?? [];
4755
+ const schema2 = p.schema ?? (content === void 0 ? {} : record2(content).schema ?? {});
4756
+ const schemaAt = p.schema !== void 0 || mediaType === void 0 ? `${parameterAt}/schema` : mediaSchemaAt(document, `${parameterAt}`, mediaType, content);
4757
+ group.properties[p.name] = parameterAt === void 0 ? () => schema2 : (api) => api(schema2, schemaAt);
4459
4758
  if (p.required || p.in === "path") group.required.push(p.name);
4460
4759
  groups.set(key, group);
4461
4760
  }
@@ -4465,7 +4764,7 @@ var compileOpenApiDocument = (entry, inputDocument, options = {}) => {
4465
4764
  properties[key] = (api) => ({
4466
4765
  type: "object",
4467
4766
  properties: Object.fromEntries(
4468
- Object.entries(group.properties).map(([name2, schema2]) => [name2, api(schema2)])
4767
+ Object.entries(group.properties).map(([name2, schema2]) => [name2, schema2(api)])
4469
4768
  ),
4470
4769
  required: group.required,
4471
4770
  additionalProperties: false
@@ -4475,16 +4774,27 @@ var compileOpenApiDocument = (entry, inputDocument, options = {}) => {
4475
4774
  const bodyVariants = [];
4476
4775
  let retainedBody;
4477
4776
  if (operation.requestBody) {
4478
- const request = Schema_exports.decodeUnknownSync(OpenapiRequestBody)(
4479
- document.resolve(operation.requestBody)
4777
+ const bodyAt = declaredAt(document, resolved2.requestBody, `${at}/requestBody`);
4778
+ const request = decodeDefinition(
4779
+ OpenapiRequestBody,
4780
+ document.resolve(operation.requestBody),
4781
+ bodyAt,
4782
+ "request body"
4480
4783
  );
4784
+ const rawContent = record2(document.resolve(record2(resolved2.requestBody)).content);
4481
4785
  const retainedContent = {};
4482
4786
  for (const [contentType, content] of Object.entries(request.content)) {
4483
4787
  const kind = openapiMediaKind(contentType);
4484
4788
  const schema2 = document.resolve(content.schema ?? {});
4485
- const declared = content.schema ?? {};
4789
+ const declared2 = content.schema ?? {};
4790
+ const schemaAt = mediaSchemaAt(
4791
+ document,
4792
+ bodyAt,
4793
+ contentType,
4794
+ rawContent[contentType]
4795
+ );
4486
4796
  const retainedFiles = {};
4487
- let inputSchema = kind === "binary" ? () => binaryInput : kind === "text" ? () => ({ type: "string" }) : (api) => api(declared);
4797
+ let inputSchema = kind === "binary" ? () => binaryInput : kind === "text" ? () => ({ type: "string" }) : (api) => api(declared2, schemaAt);
4488
4798
  if (kind === "multipart") {
4489
4799
  const fields = { ...record2(schema2.properties ?? {}) };
4490
4800
  const files = {};
@@ -4497,8 +4807,9 @@ var compileOpenApiDocument = (entry, inputDocument, options = {}) => {
4497
4807
  }
4498
4808
  }
4499
4809
  const form = schema2;
4810
+ const formAt = typeof declared2.$ref === "string" ? declared2.$ref : schemaAt;
4500
4811
  inputSchema = (api) => {
4501
- const converted = api({ ...form, properties: fields });
4812
+ const converted = api({ ...form, properties: fields }, formAt);
4502
4813
  return {
4503
4814
  ...converted,
4504
4815
  properties: { ...record2(converted.properties ?? {}), ...files }
@@ -4517,7 +4828,7 @@ var compileOpenApiDocument = (entry, inputDocument, options = {}) => {
4517
4828
  }
4518
4829
  retainedBody = { ...request, content: retainedContent };
4519
4830
  if (!bodyVariants.length)
4520
- fail2("request_body", `Tool ${name} has no request media types.`);
4831
+ fail2("request_body", "The request body declares no media types.");
4521
4832
  properties.body = () => ({});
4522
4833
  properties.contentType = () => ({
4523
4834
  type: "string",
@@ -4554,18 +4865,16 @@ var compileOpenApiDocument = (entry, inputDocument, options = {}) => {
4554
4865
  try {
4555
4866
  jsonSchema(input);
4556
4867
  } catch {
4557
- fail2(
4558
- "input_schema",
4559
- `Tool ${name} has an input schema this importer cannot preserve yet.`
4560
- );
4868
+ fail2("input_schema", "Its input schema is not valid JSON.");
4561
4869
  }
4562
- const outputSchema = responseSchema(document, operation, method);
4870
+ const outputSchema = responseSchema(document, resolved2, at, method);
4563
4871
  const streaming = Object.values(operation.responses ?? {}).some((response) => {
4564
4872
  const content = errorContent(document, response);
4565
4873
  return content !== void 0 && Object.hasOwn(record2(content), "text/event-stream");
4566
4874
  });
4567
4875
  built.push({
4568
4876
  methods: operationMethods,
4877
+ declared: { name, method, path },
4569
4878
  operation: {
4570
4879
  ...streaming ? { streaming: true } : {},
4571
4880
  ...tags === void 0 || tags.length === 0 ? {} : { tags },
@@ -4573,17 +4882,20 @@ var compileOpenApiDocument = (entry, inputDocument, options = {}) => {
4573
4882
  ...operation.operationId === void 0 ? {} : { operationId: operation.operationId },
4574
4883
  description: operation.summary ?? operation.description ?? name,
4575
4884
  method,
4576
- path,
4885
+ path: `${options.pathPrefix ?? ""}${path}`,
4577
4886
  baseUrl,
4578
4887
  openapi: spec.openapi,
4579
4888
  securitySchemes: Object.fromEntries(
4580
4889
  [...new Set(security.flatMap(Object.keys))].map((key) => [
4581
4890
  key,
4582
- schemes[key] ?? fail2("auth_method", "A security requirement names a missing scheme.")
4891
+ schemes[key] ?? fail2(
4892
+ "auth_method",
4893
+ `A security requirement names ${JSON.stringify(key)}, which securitySchemes does not declare.`
4894
+ )
4583
4895
  ])
4584
4896
  ),
4585
4897
  request: {
4586
- parameters: [...parameters.values()],
4898
+ parameters: [...parameters.values()].map(({ parameter }) => parameter),
4587
4899
  ...retainedBody === void 0 ? {} : { requestBody: retainedBody },
4588
4900
  security,
4589
4901
  responses: Object.fromEntries(
@@ -4604,14 +4916,20 @@ var compileOpenApiDocument = (entry, inputDocument, options = {}) => {
4604
4916
  },
4605
4917
  input,
4606
4918
  ...outputSchema === void 0 ? {} : { outputSchema },
4607
- errorResponses: errorResponses(document, operation)
4919
+ errorResponses: errorResponses(document, resolved2, at)
4608
4920
  }
4609
4921
  });
4610
4922
  } catch (error) {
4611
4923
  if (!(error instanceof OpenapiCompileError) && !Schema_exports.isSchemaError(error)) throw error;
4612
- const code = error instanceof OpenapiCompileError ? error.code : "invalid_document";
4613
- if (error instanceof OpenapiCompileError && !causes.has(code)) causes.set(code, error);
4614
- skipped.add(code);
4924
+ skipped.push({
4925
+ name,
4926
+ method,
4927
+ path,
4928
+ ...error instanceof OpenapiCompileError ? { code: error.code, reason: error.reason } : {
4929
+ code: "invalid_document",
4930
+ reason: `The operation at ${at} does not match OpenAPI.`
4931
+ }
4932
+ });
4615
4933
  }
4616
4934
  }
4617
4935
  const counts = /* @__PURE__ */ new Map();
@@ -4631,35 +4949,84 @@ var compileOpenApiDocument = (entry, inputDocument, options = {}) => {
4631
4949
  const best = pinnedOrigin === void 0 ? 0 : counts.get(pinnedOrigin) ?? 0;
4632
4950
  if (count > best || count === best && origin === documentOrigin) pinnedOrigin = origin;
4633
4951
  }
4634
- for (const { operation, methods: operationMethods } of built) {
4635
- if (new URL(operation.baseUrl).origin !== pinnedOrigin) {
4636
- skipped.add("multiple_hosts");
4952
+ const imported = [];
4953
+ for (const { operation, methods: operationMethods, declared } of built) {
4954
+ const origin = new URL(operation.baseUrl).origin;
4955
+ if (origin !== pinnedOrigin) {
4956
+ skipped.push({
4957
+ ...declared,
4958
+ code: "multiple_hosts",
4959
+ reason: options.allowedOrigin === void 0 ? `Its server origin ${origin} differs from ${pinnedOrigin ?? "the origin"} most operations use.` : `Its server origin ${origin} is not allowedOrigin ${options.allowedOrigin}.`
4960
+ });
4637
4961
  continue;
4638
4962
  }
4639
- operations.push(operation);
4963
+ imported.push({ operation, declared });
4640
4964
  for (const method of operationMethods) methods.set(method.name, method);
4641
4965
  }
4966
+ const operations = imported.map(({ operation }) => operation);
4642
4967
  if (!operations.length) {
4643
- const [only, ...others] = causes.values();
4644
- if (only !== void 0 && others.length === 0 && [...skipped].every((reason) => reason === only.code))
4645
- throw only;
4646
- fail2(
4647
- "no_supported_operations",
4648
- "Executor cannot import any operation in this API definition."
4649
- );
4968
+ const left = inDefinitionOrder(skipped);
4969
+ if (options.allowedOrigin !== void 0 && built.length > 0)
4970
+ throw new OpenapiCompileError({
4971
+ code: "no_supported_operations",
4972
+ reason: `allowedOrigin ${options.allowedOrigin} is not the origin of any operation. They resolve to ${[...counts.keys()].join(", ")}. Set allowedOrigin to the origin that receives the account's credentials, or set baseUrl.`,
4973
+ skipped: left,
4974
+ origins: [...counts.keys()]
4975
+ });
4976
+ const [first] = left;
4977
+ if (first !== void 0 && left.every(({ code, reason }) => code === first.code && reason === first.reason))
4978
+ throw new OpenapiCompileError({ code: first.code, reason: first.reason, skipped: left });
4979
+ throw new OpenapiCompileError({
4980
+ code: "no_supported_operations",
4981
+ reason: "Executor cannot import any operation in this API definition.",
4982
+ skipped: left
4983
+ });
4650
4984
  }
4651
- if (!operations.some(
4652
- (operation) => operation.streaming !== true && (operation.request.security.length === 0 || operation.request.security.some((requirement) => {
4653
- const keys = Object.keys(requirement);
4654
- return keys.every((key) => bindings.has(key)) || keys.length === 1 && oauth.some((method) => method.name === keys[0]);
4655
- }))
4656
- ))
4657
- fail2(
4658
- "no_supported_operations",
4659
- "This API has no operations supported by the available authentication and response transports."
4985
+ const supported = (requirement) => {
4986
+ const keys = Object.keys(requirement);
4987
+ return keys.every((key) => bindings.has(key)) || keys.length === 1 && oauth.some((method) => method.name === keys[0]);
4988
+ };
4989
+ const schemeLabel = (key) => {
4990
+ const scheme = document.resolve(record2(schemes[key]));
4991
+ const detail = scheme.type === "oauth2" ? Object.keys(record2(scheme.flows ?? {})).join("/") : scheme.type === "http" ? scheme.scheme : scheme.type === "apiKey" ? `in ${String(scheme.in)}` : void 0;
4992
+ return `${key} (${[scheme.type, detail].filter((part) => typeof part === "string" && part !== "").join(" ")})`;
4993
+ };
4994
+ const unsupportedBy = (requirement) => {
4995
+ const keys = Object.keys(requirement);
4996
+ const unknown = keys.filter(
4997
+ (key) => !bindings.has(key) && !oauth.some((method) => method.name === key)
4660
4998
  );
4999
+ return unknown.length > 0 ? unknown.map(schemeLabel).join(" and ") : `${keys.map(schemeLabel).join(" with ")}, which combines OAuth sign-in with another scheme`;
5000
+ };
5001
+ const unusable = (operation) => {
5002
+ const { security } = operation.request;
5003
+ if (operation.streaming === true)
5004
+ return {
5005
+ code: "event_stream",
5006
+ reason: "It responds with text/event-stream, which a tool cannot return. Write a subscription for it."
5007
+ };
5008
+ if (security.length === 0 || security.some(supported)) return void 0;
5009
+ return {
5010
+ code: "auth_method",
5011
+ reason: `Executor cannot authenticate with any of its security requirements: ${security.map(unsupportedBy).join("; ")}.`
5012
+ };
5013
+ };
5014
+ if (imported.every(({ operation }) => unusable(operation) !== void 0))
5015
+ throw new OpenapiCompileError({
5016
+ code: "no_supported_operations",
5017
+ reason: "No operation can be called: each one is left out, streams its response, or needs authentication Executor does not support.",
5018
+ skipped: inDefinitionOrder([
5019
+ ...skipped,
5020
+ ...imported.flatMap(({ operation, declared }) => {
5021
+ const cause = unusable(operation);
5022
+ return cause === void 0 ? [] : [{ ...declared, ...cause }];
5023
+ })
5024
+ ])
5025
+ });
4661
5026
  return {
4662
5027
  operations,
5028
+ /** Declared operations left out of `operations`, in definition order. */
5029
+ skipped: inDefinitionOrder(skipped),
4663
5030
  definitions: Object.fromEntries(document.definitions),
4664
5031
  methods: Object.fromEntries(
4665
5032
  [...methods.values()].map((method) => [method.name, method.bindings])
@@ -4671,11 +5038,14 @@ var compileOpenApiDocument = (entry, inputDocument, options = {}) => {
4671
5038
  fallback
4672
5039
  };
4673
5040
  },
4674
- catch: (error) => error instanceof OpenapiCompileError ? error : new OpenapiCompileError({
4675
- code: "invalid_document",
4676
- reason: "This API definition could not be read. It may contain unsupported OpenAPI features."
4677
- })
4678
- });
5041
+ // Expected failures stay typed; anything else is a defect in the importer, not the definition.
5042
+ catch: (error) => error instanceof OpenapiCompileError ? Exit_exports.fail(error) : Schema_exports.isSchemaError(error) ? Exit_exports.fail(
5043
+ new OpenapiCompileError({
5044
+ code: "invalid_document",
5045
+ reason: "The definition's structure does not match OpenAPI."
5046
+ })
5047
+ ) : Exit_exports.die(error)
5048
+ }).pipe(Effect_exports.catch((exit) => exit));
4679
5049
  };
4680
5050
 
4681
5051
  // src/implementation/openapi-source.ts
@@ -4684,6 +5054,7 @@ var Manifest = Schema_exports.Struct({
4684
5054
  summaries: Schema_exports.Number,
4685
5055
  operations: Schema_exports.Number,
4686
5056
  definitions: Schema_exports.Number,
5057
+ skipped: Schema_exports.Number,
4687
5058
  meta: Schema_exports.optionalKey(RouterMeta)
4688
5059
  });
4689
5060
  var OperationSummary = Schema_exports.Struct({
@@ -4815,7 +5186,7 @@ var decodeText = (decoder) => (text) => Effect_exports.try({ try: () => JSON.par
4815
5186
  var schema = (decoder) => wrap(decoder, false);
4816
5187
  var invoke = (work) => Effect_exports.tryPromise({ try: work, catch: (error) => error });
4817
5188
  var invalid = () => new OpenapiError({ reason: "invalid_definition" });
4818
- var format = "openapi-v3";
5189
+ var format = "openapi-v4";
4819
5190
  var writeEntries = cacheLimits.batchEntries;
4820
5191
  var writeBytes = cacheLimits.batchBytes - 5e5;
4821
5192
  var readParts = Math.floor(cacheLimits.batchBytes / bucketLimit);
@@ -4969,6 +5340,7 @@ var liveOpenapiRouter = (options) => {
4969
5340
  source: options.source,
4970
5341
  allowedOrigin: options.allowedOrigin,
4971
5342
  baseUrl: options.baseUrl,
5343
+ pathPrefix: options.pathPrefix,
4972
5344
  securitySchemes: options.securitySchemes,
4973
5345
  patches: options.patches,
4974
5346
  fallbackSecurity: options.fallbackSecurity
@@ -5016,7 +5388,8 @@ var liveOpenapiRouter = (options) => {
5016
5388
  allowedOrigin: options.allowedOrigin,
5017
5389
  securitySchemes: options.securitySchemes,
5018
5390
  ...options.fallbackSecurity === void 0 ? {} : { fallbackSecurity: options.fallbackSecurity },
5019
- ...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl }
5391
+ ...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl },
5392
+ ...options.pathPrefix === void 0 ? {} : { pathPrefix: options.pathPrefix }
5020
5393
  }
5021
5394
  );
5022
5395
  return { revision, meta, compiled };
@@ -5029,10 +5402,12 @@ var liveOpenapiRouter = (options) => {
5029
5402
  compiled.operations.map((operation) => [operation.name, operation])
5030
5403
  );
5031
5404
  const definitions = bucketize(Object.entries(compiled.definitions));
5405
+ const skipped = compiled.skipped.length === 0 ? [] : bucketize(compiled.skipped.map((operation) => [operation.name, operation]));
5032
5406
  const parts = [
5033
5407
  ...summaries.map((value, name) => ({ kind: "summaries", name, value })),
5034
5408
  ...operations.map((value, name) => ({ kind: "operations", name, value })),
5035
- ...definitions.map((value, name) => ({ kind: "definitions", name, value }))
5409
+ ...definitions.map((value, name) => ({ kind: "definitions", name, value })),
5410
+ ...skipped.map((value, name) => ({ kind: "skipped", name, value }))
5036
5411
  ];
5037
5412
  const batches = [];
5038
5413
  let batch = [];
@@ -5061,6 +5436,7 @@ var liveOpenapiRouter = (options) => {
5061
5436
  summaries: summaries.length,
5062
5437
  operations: operations.length,
5063
5438
  definitions: definitions.length,
5439
+ skipped: skipped.length,
5064
5440
  ...meta === void 0 ? {} : { meta }
5065
5441
  },
5066
5442
  batches,
@@ -5169,6 +5545,24 @@ var liveOpenapiRouter = (options) => {
5169
5545
  value: value === void 0 ? void 0 : yield* Schema_exports.decodeUnknownEffect(OpenapiOperation)(value)
5170
5546
  };
5171
5547
  });
5548
+ const leftOutFor = (manifest, name) => Effect_exports.gen(function* () {
5549
+ if (manifest.skipped === 0) return { value: void 0 };
5550
+ const [bucket] = yield* read(
5551
+ manifest.revision,
5552
+ "skipped",
5553
+ [bucketOf(name, manifest.skipped)],
5554
+ Bucket
5555
+ );
5556
+ if (bucket === void 0) return void 0;
5557
+ const value = Object.hasOwn(bucket, name) ? bucket[name] : void 0;
5558
+ if (value === void 0) return { value: void 0 };
5559
+ const operation = yield* Schema_exports.decodeUnknownEffect(OpenapiSkippedOperation)(value);
5560
+ return yield* new OpenapiCompileError({
5561
+ code: operation.code,
5562
+ reason: operation.reason,
5563
+ skipped: [operation]
5564
+ });
5565
+ });
5172
5566
  const operationsFor = (manifest) => Effect_exports.gen(function* () {
5173
5567
  const summaries = yield* summariesFor(manifest);
5174
5568
  if (summaries === void 0) return void 0;
@@ -5223,7 +5617,7 @@ var liveOpenapiRouter = (options) => {
5223
5617
  const kindOf = (op) => {
5224
5618
  const key = op.operationId ?? op.name;
5225
5619
  const kinds = options.kinds ?? {};
5226
- return Object.hasOwn(kinds, key) ? kinds[key] ?? "mutation" : ["GET", "HEAD", "OPTIONS"].includes(op.method) ? "query" : "mutation";
5620
+ return Object.hasOwn(kinds, key) ? kinds[key] ?? "mutation" : isOpenapiReadMethod(op.method) ? "query" : "mutation";
5227
5621
  };
5228
5622
  const readOnly = (op) => kindOf(op) === "query";
5229
5623
  const summarize = (operation) => ({
@@ -5256,7 +5650,7 @@ var liveOpenapiRouter = (options) => {
5256
5650
  if (operation === void 0) {
5257
5651
  const found = yield* operationFor(manifest, name);
5258
5652
  if (found === void 0) return void 0;
5259
- if (found.value === void 0) return { value: void 0 };
5653
+ if (found.value === void 0) return yield* leftOutFor(manifest, name);
5260
5654
  operation = found.value;
5261
5655
  }
5262
5656
  const definitions = memo?.definitions ?? (yield* definitionsFor(manifest, operation));
@@ -5301,7 +5695,7 @@ var liveOpenapiRouter = (options) => {
5301
5695
  (manifest) => Effect_exports.gen(function* () {
5302
5696
  const found = yield* operationFor(manifest, name);
5303
5697
  if (found === void 0) return void 0;
5304
- if (found.value === void 0) return { value: void 0 };
5698
+ if (found.value === void 0) return yield* leftOutFor(manifest, name);
5305
5699
  const operation = found.value;
5306
5700
  if (!createRequest(options).available(operation, options.account))
5307
5701
  return { value: void 0 };
@@ -5325,6 +5719,7 @@ export {
5325
5719
  OpenapiErrorResponse,
5326
5720
  OpenapiParameter,
5327
5721
  OpenapiRequestBody,
5722
+ OpenapiSkippedOperation,
5328
5723
  isOpenapiTextMedia,
5329
5724
  liveOpenapiRouter,
5330
5725
  openapiBinaryResultSchema,