@opencode-ai/codemode 0.0.0-beta-17492

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 (72) hide show
  1. package/README.md +168 -0
  2. package/dist/codemode.d.ts +148 -0
  3. package/dist/codemode.js +70 -0
  4. package/dist/index.d.ts +5 -0
  5. package/dist/index.js +5 -0
  6. package/dist/interpreter/errors.d.ts +9 -0
  7. package/dist/interpreter/errors.js +91 -0
  8. package/dist/interpreter/execute.d.ts +4 -0
  9. package/dist/interpreter/execute.js +178 -0
  10. package/dist/interpreter/iterator.d.ts +13 -0
  11. package/dist/interpreter/iterator.js +4 -0
  12. package/dist/interpreter/methods.d.ts +17 -0
  13. package/dist/interpreter/methods.js +1026 -0
  14. package/dist/interpreter/model.d.ts +151 -0
  15. package/dist/interpreter/model.js +186 -0
  16. package/dist/interpreter/promises.d.ts +29 -0
  17. package/dist/interpreter/promises.js +253 -0
  18. package/dist/interpreter/references.d.ts +6 -0
  19. package/dist/interpreter/references.js +114 -0
  20. package/dist/interpreter/runtime.d.ts +98 -0
  21. package/dist/interpreter/runtime.js +2351 -0
  22. package/dist/interpreter/scope.d.ts +15 -0
  23. package/dist/interpreter/scope.js +79 -0
  24. package/dist/interpreter/transpile.node.d.ts +5 -0
  25. package/dist/interpreter/transpile.node.js +19 -0
  26. package/dist/interpreter/transpile.workerd.d.ts +5 -0
  27. package/dist/interpreter/transpile.workerd.js +6 -0
  28. package/dist/openapi/index.d.ts +7 -0
  29. package/dist/openapi/index.js +101 -0
  30. package/dist/openapi/runtime.d.ts +4 -0
  31. package/dist/openapi/runtime.js +283 -0
  32. package/dist/openapi/spec.d.ts +20 -0
  33. package/dist/openapi/spec.js +588 -0
  34. package/dist/openapi/types.d.ts +122 -0
  35. package/dist/openapi/types.js +2 -0
  36. package/dist/stdlib/collections.d.ts +4 -0
  37. package/dist/stdlib/collections.js +57 -0
  38. package/dist/stdlib/console.d.ts +2 -0
  39. package/dist/stdlib/console.js +126 -0
  40. package/dist/stdlib/date.d.ts +7 -0
  41. package/dist/stdlib/date.js +186 -0
  42. package/dist/stdlib/json.d.ts +6 -0
  43. package/dist/stdlib/json.js +124 -0
  44. package/dist/stdlib/math.d.ts +12 -0
  45. package/dist/stdlib/math.js +157 -0
  46. package/dist/stdlib/number.d.ts +6 -0
  47. package/dist/stdlib/number.js +76 -0
  48. package/dist/stdlib/object.d.ts +7 -0
  49. package/dist/stdlib/object.js +100 -0
  50. package/dist/stdlib/promise.d.ts +2 -0
  51. package/dist/stdlib/promise.js +1 -0
  52. package/dist/stdlib/regexp.d.ts +11 -0
  53. package/dist/stdlib/regexp.js +106 -0
  54. package/dist/stdlib/string.d.ts +4 -0
  55. package/dist/stdlib/string.js +48 -0
  56. package/dist/stdlib/url.d.ts +12 -0
  57. package/dist/stdlib/url.js +84 -0
  58. package/dist/stdlib/value.d.ts +12 -0
  59. package/dist/stdlib/value.js +120 -0
  60. package/dist/tool-error.d.ts +11 -0
  61. package/dist/tool-error.js +9 -0
  62. package/dist/tool-runtime.d.ts +68 -0
  63. package/dist/tool-runtime.js +390 -0
  64. package/dist/tool-schema.d.ts +15 -0
  65. package/dist/tool-schema.js +213 -0
  66. package/dist/tool.d.ts +55 -0
  67. package/dist/tool.js +21 -0
  68. package/dist/tools.d.ts +4 -0
  69. package/dist/tools.js +1 -0
  70. package/dist/values.d.ts +31 -0
  71. package/dist/values.js +50 -0
  72. package/package.json +46 -0
@@ -0,0 +1,588 @@
1
+ import { fromSchemaOpenApi3_0, fromSchemaOpenApi3_1 } from "effect/JsonSchema";
2
+ import { isBlockedMember } from "../tool-runtime.js";
3
+ export const methods = new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
4
+ const parameterLocations = ["path", "query", "header"];
5
+ const ignoredHeaderParameters = new Set(["accept", "content-type", "authorization"]);
6
+ export const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7
+ const asArray = (value) => (Array.isArray(value) ? value : []);
8
+ export const nonEmptyString = (value) => typeof value === "string" && value !== "" ? value : undefined;
9
+ // Spec- and model-controlled keys must not resolve inherited properties.
10
+ export const own = (record, key) => Object.hasOwn(record, key) ? record[key] : undefined;
11
+ const resolvePointer = (root, ref) => ref
12
+ .slice(2)
13
+ .split("/")
14
+ .map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~"))
15
+ .reduce((item, segment) => (isRecord(item) ? own(item, segment) : undefined), root);
16
+ export const resolve = (document, value) => {
17
+ const next = (current, seen) => {
18
+ if (!isRecord(current))
19
+ return current;
20
+ const ref = nonEmptyString(own(current, "$ref"));
21
+ if (ref === undefined || !ref.startsWith("#/") || seen.has(ref))
22
+ return current;
23
+ const target = resolvePointer(document, ref);
24
+ return target === undefined ? current : next(target, new Set([...seen, ref]));
25
+ };
26
+ return next(value, new Set());
27
+ };
28
+ const hiddenKeyword = { request: "readOnly", response: "writeOnly" };
29
+ // Resolves one `$ref` hop so every link of a chain has its own sibling declarations
30
+ // inspected; cycles terminate in the callers' cycle solver. Local `$defs`/`definitions`
31
+ // pointers resolve against the schema being projected, other pointers rebase onto the target.
32
+ const resolveResource = (document, resource) => {
33
+ if (!isRecord(resource.value))
34
+ return resource;
35
+ const ref = nonEmptyString(own(resource.value, "$ref"));
36
+ if (ref === undefined || !ref.startsWith("#/"))
37
+ return resource;
38
+ const local = ref.startsWith("#/$defs/") || ref.startsWith("#/definitions/");
39
+ const target = resolvePointer(local ? resource.root : document, ref);
40
+ if (target === undefined)
41
+ return resource;
42
+ return { value: target, root: local ? resource.root : target };
43
+ };
44
+ const emptyCache = () => ({
45
+ hidden: { values: new Map(), pending: new Map(), stack: [] },
46
+ names: { values: new Map(), pending: new Map(), stack: [] },
47
+ });
48
+ const projectionCaches = new WeakMap();
49
+ const projectionCache = (document, direction) => {
50
+ const existing = projectionCaches.get(document);
51
+ if (existing !== undefined)
52
+ return existing[direction];
53
+ const created = { request: emptyCache(), response: emptyCache() };
54
+ projectionCaches.set(document, created);
55
+ return created[direction];
56
+ };
57
+ const solveCycles = (solver, key, provisional, scope, compute) => {
58
+ const cached = solver.values.get(key);
59
+ if (cached !== undefined)
60
+ return cached;
61
+ const pending = solver.pending.get(key);
62
+ if (pending !== undefined) {
63
+ scope.lowlink = Math.min(scope.lowlink, pending);
64
+ return provisional;
65
+ }
66
+ // Components pop as contiguous stack suffixes, so pending indices stay 0..size-1.
67
+ const index = solver.pending.size;
68
+ const base = solver.stack.length;
69
+ solver.pending.set(key, index);
70
+ solver.stack.push(key);
71
+ const inner = { lowlink: Infinity };
72
+ const value = compute(inner);
73
+ if (inner.lowlink < index) {
74
+ scope.lowlink = Math.min(scope.lowlink, inner.lowlink);
75
+ return value;
76
+ }
77
+ for (const member of solver.stack.splice(base)) {
78
+ solver.pending.delete(member);
79
+ solver.values.set(member, value);
80
+ }
81
+ return value;
82
+ };
83
+ // Most documents have no directional keywords; one cached scan skips projection entirely.
84
+ const directionalDocuments = new WeakMap();
85
+ export const hasDirectionalSchemas = (document) => {
86
+ const cached = directionalDocuments.get(document);
87
+ if (cached !== undefined)
88
+ return cached;
89
+ const contains = (value) => {
90
+ if (Array.isArray(value))
91
+ return value.some(contains);
92
+ if (!isRecord(value))
93
+ return false;
94
+ if (own(value, "readOnly") === true || own(value, "writeOnly") === true)
95
+ return true;
96
+ return Object.values(value).some(contains);
97
+ };
98
+ const result = contains(document);
99
+ directionalDocuments.set(document, result);
100
+ return result;
101
+ };
102
+ // OpenAPI 3.1 allows keywords as siblings of `$ref`, so a schema's own declarations
103
+ // are inspected before following the reference.
104
+ const isHidden = (document, resource, direction, scope = { lowlink: Infinity }) => {
105
+ const value = resource.value;
106
+ if (!isRecord(value))
107
+ return false;
108
+ if (own(value, hiddenKeyword[direction]) === true)
109
+ return true;
110
+ return solveCycles(projectionCache(document, direction).hidden, value, false, scope, (inner) => {
111
+ const target = resolveResource(document, resource);
112
+ return (asArray(own(value, "allOf")).some((item) => isHidden(document, { ...resource, value: item }, direction, inner)) ||
113
+ (target.value !== value && isHidden(document, target, direction, inner)));
114
+ });
115
+ };
116
+ // Hidden property names declared by a schema itself or inherited through `$ref` and
117
+ // `allOf` composition, so sibling `required` lists stay consistent after projection.
118
+ const hiddenNames = (document, resource, direction, scope = { lowlink: Infinity }) => {
119
+ const value = resource.value;
120
+ if (!isRecord(value))
121
+ return new Set();
122
+ return solveCycles(projectionCache(document, direction).names, value, new Set(), scope, (inner) => {
123
+ const properties = own(value, "properties");
124
+ const declared = isRecord(properties)
125
+ ? Object.entries(properties)
126
+ .filter(([, property]) => isHidden(document, { ...resource, value: property }, direction))
127
+ .map(([name]) => name)
128
+ : [];
129
+ const composed = asArray(own(value, "allOf")).flatMap((item) => [
130
+ ...hiddenNames(document, { ...resource, value: item }, direction, inner),
131
+ ]);
132
+ const target = resolveResource(document, resource);
133
+ const referenced = target.value === value ? [] : hiddenNames(document, target, direction, inner);
134
+ return new Set([...declared, ...composed, ...referenced]);
135
+ });
136
+ };
137
+ // `not`/`if`/`contains` subschemas pass through unprojected: they negate or select
138
+ // rather than assert, so removing hidden properties would invert their semantics.
139
+ const nestedSchemas = new Set([
140
+ "items",
141
+ "additionalProperties",
142
+ "unevaluatedProperties",
143
+ "propertyNames",
144
+ "then",
145
+ "else",
146
+ ]);
147
+ const nestedSchemaLists = new Set(["anyOf", "oneOf", "prefixItems"]);
148
+ const nestedSchemaMaps = new Set(["patternProperties", "dependentSchemas", "$defs", "definitions"]);
149
+ const directionalSchema = (document, resource, direction, excluded = new Set()) => {
150
+ if (!isRecord(resource.value))
151
+ return resource.value;
152
+ const hidden = new Set([...excluded, ...hiddenNames(document, resource, direction)]);
153
+ const project = (item, inherited = new Set()) => directionalSchema(document, { ...resource, value: item }, direction, inherited);
154
+ return Object.fromEntries(Object.entries(resource.value).map(([key, item]) => {
155
+ if (key === "properties" && isRecord(item)) {
156
+ return [
157
+ key,
158
+ Object.fromEntries(Object.entries(item)
159
+ .filter(([name]) => !hidden.has(name))
160
+ .map(([name, property]) => [name, project(property)])),
161
+ ];
162
+ }
163
+ if (key === "required" && Array.isArray(item)) {
164
+ return [key, item.filter((name) => typeof name !== "string" || !hidden.has(name))];
165
+ }
166
+ // allOf branches share one object; hidden names apply across every branch.
167
+ if (key === "allOf" && Array.isArray(item))
168
+ return [key, item.map((entry) => project(entry, hidden))];
169
+ if (nestedSchemas.has(key))
170
+ return [key, project(item)];
171
+ if (nestedSchemaLists.has(key) && Array.isArray(item))
172
+ return [key, item.map((entry) => project(entry))];
173
+ if (nestedSchemaMaps.has(key) && isRecord(item)) {
174
+ return [key, Object.fromEntries(Object.entries(item).map(([name, entry]) => [name, project(entry)]))];
175
+ }
176
+ return [key, item];
177
+ }));
178
+ };
179
+ const normalizeSchema = (document, value) => {
180
+ if (!isRecord(value))
181
+ return {};
182
+ const normalized = nonEmptyString(document.openapi)?.startsWith("3.0")
183
+ ? fromSchemaOpenApi3_0(value)
184
+ : fromSchemaOpenApi3_1(value);
185
+ return Object.keys(normalized.definitions).length === 0
186
+ ? normalized.schema
187
+ : { ...normalized.schema, $defs: normalized.definitions };
188
+ };
189
+ const projectSchema = (document, value, direction) => normalizeSchema(document, hasDirectionalSchemas(document) ? directionalSchema(document, { value, root: value }, direction) : value);
190
+ export const componentDefinitions = (document, direction) => {
191
+ const components = isRecord(document.components) ? document.components : {};
192
+ const schemas = isRecord(components.schemas) ? components.schemas : {};
193
+ return Object.fromEntries(Object.entries(schemas).map(([name, value]) => [name, projectSchema(document, value, direction)]));
194
+ };
195
+ const withDefinitions = (schema, definitions) => {
196
+ if (Object.keys(definitions).length === 0)
197
+ return schema;
198
+ const local = isRecord(schema.$defs) ? schema.$defs : {};
199
+ return { ...schema, $defs: { ...definitions, ...local } };
200
+ };
201
+ const isJsonMediaType = (mediaType) => {
202
+ const normalized = mediaType.split(";")[0]?.trim().toLowerCase() ?? "";
203
+ return normalized === "application/json" || normalized.endsWith("+json");
204
+ };
205
+ const isBinaryMediaType = (document, mediaType, value) => {
206
+ const normalized = mediaType.split(";")[0]?.trim().toLowerCase() ?? "";
207
+ if (!isJsonMediaType(normalized) && !normalized.startsWith("text/"))
208
+ return true;
209
+ if (!isRecord(value))
210
+ return false;
211
+ const schema = resolve(document, value.schema);
212
+ return isRecord(schema) && schema.format === "binary";
213
+ };
214
+ const jsonContent = (content) => {
215
+ const entry = Object.entries(content).find(([mediaType]) => isJsonMediaType(mediaType));
216
+ return entry !== undefined && isRecord(entry[1]) ? { mediaType: entry[0], schema: entry[1].schema } : undefined;
217
+ };
218
+ const isFlattenableObjectBody = (schema, requestRequired) => isRecord(schema) &&
219
+ requestRequired &&
220
+ schema.type === "object" &&
221
+ isRecord(schema.properties) &&
222
+ schema.additionalProperties === false &&
223
+ schema.nullable !== true &&
224
+ schema.allOf === undefined &&
225
+ schema.anyOf === undefined &&
226
+ schema.oneOf === undefined;
227
+ const operationParameters = (document, pathItem, operation) => {
228
+ // OpenAPI operation parameters override path parameters with the same location and name.
229
+ const declared = new Map();
230
+ for (const raw of [...asArray(pathItem.parameters), ...asArray(operation.parameters)]) {
231
+ const resolved = resolve(document, raw);
232
+ if (!isRecord(resolved))
233
+ return { ok: false, reason: "parameter declaration is invalid or unresolved" };
234
+ const name = nonEmptyString(resolved.name);
235
+ const location = nonEmptyString(resolved.in);
236
+ if (name === undefined || location === undefined)
237
+ return { ok: false, reason: "parameter declaration is missing name or location" };
238
+ declared.set(`${location}:${name}`, { name, location, parameter: resolved });
239
+ }
240
+ const unordered = [];
241
+ for (const item of declared.values()) {
242
+ const name = item.name;
243
+ const location = item.location;
244
+ const resolved = item.parameter;
245
+ if (location === "cookie")
246
+ return { ok: false, reason: `cookie parameter '${name}' is not supported` };
247
+ if (location !== "path" && location !== "query" && location !== "header") {
248
+ return { ok: false, reason: `parameter '${name}' uses unsupported location '${location}'` };
249
+ }
250
+ if (location === "header" && ignoredHeaderParameters.has(name.toLowerCase()))
251
+ continue;
252
+ if (resolved.schema === undefined && resolved.content === undefined) {
253
+ return { ok: false, reason: `parameter '${name}' declares neither schema nor content` };
254
+ }
255
+ if (resolved.content !== undefined)
256
+ return { ok: false, reason: `parameter '${name}' uses unsupported content encoding` };
257
+ if (resolved.style !== undefined && nonEmptyString(resolved.style) === undefined) {
258
+ return { ok: false, reason: `parameter '${name}' has an invalid style` };
259
+ }
260
+ if (resolved.explode !== undefined && typeof resolved.explode !== "boolean") {
261
+ return { ok: false, reason: `parameter '${name}' has an invalid explode value` };
262
+ }
263
+ if (resolved.allowReserved !== undefined && typeof resolved.allowReserved !== "boolean") {
264
+ return { ok: false, reason: `parameter '${name}' has an invalid allowReserved value` };
265
+ }
266
+ if (resolved.allowReserved === true)
267
+ return { ok: false, reason: `parameter '${name}' uses unsupported allowReserved encoding` };
268
+ const declaredStyle = nonEmptyString(resolved.style) ?? (location === "query" ? "form" : "simple");
269
+ if (location === "query" && declaredStyle !== "form" && declaredStyle !== "deepObject") {
270
+ return { ok: false, reason: `query parameter '${name}' uses unsupported style '${declaredStyle}'` };
271
+ }
272
+ if (location !== "query" && declaredStyle !== "simple") {
273
+ return { ok: false, reason: `${location} parameter '${name}' uses unsupported style '${declaredStyle}'` };
274
+ }
275
+ const style = declaredStyle === "deepObject" ? "deepObject" : declaredStyle === "form" ? "form" : "simple";
276
+ const explode = typeof resolved.explode === "boolean" ? resolved.explode : style === "form";
277
+ if (style === "deepObject" && !explode) {
278
+ return { ok: false, reason: `query parameter '${name}' uses deepObject with explode=false` };
279
+ }
280
+ const base = projectSchema(document, resolved.schema, "request");
281
+ const description = nonEmptyString(resolved.description);
282
+ unordered.push({
283
+ name,
284
+ location,
285
+ required: resolved.required === true || location === "path",
286
+ style,
287
+ explode,
288
+ schema: {
289
+ ...base,
290
+ ...(base.description === undefined && description !== undefined ? { description } : {}),
291
+ },
292
+ });
293
+ }
294
+ return {
295
+ ok: true,
296
+ value: parameterLocations.flatMap((location) => unordered.filter((field) => field.location === location)),
297
+ };
298
+ };
299
+ const operationBody = (document, operation) => {
300
+ const resolved = resolve(document, operation.requestBody);
301
+ if (!isRecord(resolved))
302
+ return { ok: true, value: { fields: [], body: undefined } };
303
+ const content = isRecord(resolved.content) ? resolved.content : {};
304
+ const selected = jsonContent(content);
305
+ if (selected === undefined) {
306
+ return {
307
+ ok: false,
308
+ reason: `request body has no JSON content (declared: ${Object.keys(content).join(", ") || "none"})`,
309
+ };
310
+ }
311
+ const resolvedSchema = resolve(document, selected.schema);
312
+ const schema = hasDirectionalSchemas(document)
313
+ ? directionalSchema(document, { value: resolvedSchema, root: resolvedSchema }, "request")
314
+ : resolvedSchema;
315
+ const required = resolved.required === true;
316
+ if (!isFlattenableObjectBody(schema, required)) {
317
+ return {
318
+ ok: true,
319
+ value: {
320
+ fields: [
321
+ {
322
+ name: "body",
323
+ location: "body",
324
+ required,
325
+ schema: projectSchema(document, selected.schema, "request"),
326
+ style: undefined,
327
+ explode: undefined,
328
+ },
329
+ ],
330
+ body: { required, mode: "value", mediaType: selected.mediaType },
331
+ },
332
+ };
333
+ }
334
+ const requiredProperties = new Set(Array.isArray(schema.required) ? schema.required.filter((item) => typeof item === "string") : []);
335
+ return {
336
+ ok: true,
337
+ value: {
338
+ // Field schemas were already projected with the body as resolution root; a second
339
+ // directional pass rooted at the field would misresolve shadowed local $defs.
340
+ fields: Object.entries(schema.properties).map(([name, value]) => ({
341
+ name,
342
+ location: "body",
343
+ required: required && requiredProperties.has(name),
344
+ schema: normalizeSchema(document, value),
345
+ style: undefined,
346
+ explode: undefined,
347
+ })),
348
+ body: { required, mode: "object", mediaType: selected.mediaType },
349
+ },
350
+ };
351
+ };
352
+ export const operationInput = (document, pathItem, operation) => {
353
+ const parameters = operationParameters(document, pathItem, operation);
354
+ if (!parameters.ok)
355
+ return parameters;
356
+ const requestBody = operationBody(document, operation);
357
+ if (!requestBody.ok)
358
+ return requestBody;
359
+ const fields = [...parameters.value, ...requestBody.value.fields];
360
+ const conflicts = new Set([...Map.groupBy(fields, (field) => field.name)]
361
+ .filter(([, matches]) => new Set(matches.map((field) => field.location)).size > 1)
362
+ .map(([name]) => name));
363
+ const used = new Set();
364
+ return {
365
+ ok: true,
366
+ value: {
367
+ fields: fields.map((field) => {
368
+ const visibleName = isBlockedMember(field.name) ? `${field.name}_2` : field.name;
369
+ const base = conflicts.has(field.name) ? `${field.location}_${visibleName}` : visibleName;
370
+ const next = (index) => {
371
+ const candidate = index === 1 ? base : `${base}_${index}`;
372
+ return used.has(candidate) ? next(index + 1) : candidate;
373
+ };
374
+ const inputName = next(1);
375
+ used.add(inputName);
376
+ return { ...field, inputName };
377
+ }),
378
+ body: requestBody.value.body,
379
+ },
380
+ };
381
+ };
382
+ export const inputSchema = (fields, definitions) => {
383
+ const required = fields.filter((field) => field.required).map((field) => field.inputName);
384
+ return withDefinitions({
385
+ type: "object",
386
+ properties: Object.fromEntries(fields.map((field) => [field.inputName, field.schema])),
387
+ ...(required.length === 0 ? {} : { required }),
388
+ }, definitions);
389
+ };
390
+ const successfulResponses = (document, operation) => {
391
+ if (!isRecord(operation.responses))
392
+ return { ok: true, value: [] };
393
+ const entries = Object.entries(operation.responses);
394
+ const selected = [
395
+ ...entries.filter(([status]) => /^2\d\d$/.test(status)).sort(([a], [b]) => a.localeCompare(b)),
396
+ ...entries.filter(([status]) => status.toUpperCase() === "2XX"),
397
+ ];
398
+ const responses = [];
399
+ for (const [, value] of selected) {
400
+ const resolved = resolve(document, value);
401
+ if (!isRecord(resolved) || nonEmptyString(resolved.$ref) !== undefined) {
402
+ return { ok: false, reason: "successful response declaration is invalid or unresolved" };
403
+ }
404
+ responses.push(resolved);
405
+ }
406
+ return { ok: true, value: responses };
407
+ };
408
+ export const operationOutput = (document, operation, definitions) => {
409
+ if (operation["x-websocket"] === true)
410
+ return { ok: false, reason: "WebSocket operations are not supported" };
411
+ const responses = successfulResponses(document, operation);
412
+ if (!responses.ok)
413
+ return responses;
414
+ const streams = responses.value.some((response) => isRecord(response.content) &&
415
+ Object.keys(response.content).some((mediaType) => mediaType.split(";")[0]?.trim().toLowerCase() === "text/event-stream"));
416
+ if (streams)
417
+ return { ok: false, reason: "SSE operations are not supported" };
418
+ const binary = responses.value.some((response) => isRecord(response.content) &&
419
+ Object.entries(response.content).some(([mediaType, value]) => isBinaryMediaType(document, mediaType, value)));
420
+ if (binary)
421
+ return { ok: false, reason: "binary responses are not supported" };
422
+ const outcomes = [];
423
+ for (const response of responses.value) {
424
+ if (response.content !== undefined && !isRecord(response.content))
425
+ return { ok: true, value: undefined };
426
+ const content = isRecord(response.content) ? response.content : {};
427
+ if (Object.keys(content).length === 0) {
428
+ outcomes.push({ type: "null" });
429
+ continue;
430
+ }
431
+ for (const [mediaType, value] of Object.entries(content)) {
432
+ if (!isJsonMediaType(mediaType)) {
433
+ outcomes.push({ type: "string" });
434
+ continue;
435
+ }
436
+ if (!isRecord(value) || value.schema === undefined)
437
+ return { ok: true, value: undefined };
438
+ outcomes.push(projectSchema(document, value.schema, "response"));
439
+ }
440
+ }
441
+ if (outcomes.length === 0)
442
+ return { ok: true, value: undefined };
443
+ return {
444
+ ok: true,
445
+ value: withDefinitions(outcomes.length === 1 ? (outcomes[0] ?? {}) : { anyOf: outcomes }, definitions),
446
+ };
447
+ };
448
+ const sanitizeOperationSegment = (raw) => {
449
+ const base = raw
450
+ .replaceAll(/[^A-Za-z0-9_$]+/g, "_")
451
+ .replace(/^_+|_+$/g, "")
452
+ .replace(/^([0-9])/, "_$1") || "operation";
453
+ return isBlockedMember(base) ? `${base}_2` : base;
454
+ };
455
+ const fallbackOperationId = (method, path) => [
456
+ method,
457
+ ...path
458
+ .split("/")
459
+ .filter((part) => part !== "")
460
+ .flatMap((part) => (part.startsWith("{") && part.endsWith("}") ? ["by", part.slice(1, -1)] : [part]))
461
+ .flatMap((part) => part.split(/[^A-Za-z0-9]+/).filter((word) => word !== "")),
462
+ ]
463
+ .map((word, index) => {
464
+ const lower = word.toLowerCase();
465
+ return index === 0 ? lower : `${lower.charAt(0).toUpperCase()}${lower.slice(1)}`;
466
+ })
467
+ .join("");
468
+ export const operationPath = (method, path, operation, used, namespaces) => {
469
+ const raw = nonEmptyString(operation.operationId);
470
+ const segments = (raw === undefined ? [fallbackOperationId(method, path)] : raw.split(".")).map(sanitizeOperationSegment);
471
+ if (isOperationPathAvailable(segments, used, namespaces))
472
+ return segments;
473
+ const conflict = segments.slice(0, -1).findIndex((_, index) => used.has(segments.slice(0, index + 1).join(".")));
474
+ if (conflict >= 0 && conflict + 1 < segments.length) {
475
+ const collapsed = segments.flatMap((segment, index) => {
476
+ if (index === conflict) {
477
+ const next = segments[index + 1] ?? "";
478
+ return [`${segment}${next.charAt(0).toUpperCase()}${next.slice(1)}`];
479
+ }
480
+ return index === conflict + 1 ? [] : [segment];
481
+ });
482
+ if (isOperationPathAvailable(collapsed, used, namespaces))
483
+ return collapsed;
484
+ }
485
+ const fallback = segments.join("_");
486
+ const next = (index) => {
487
+ const candidate = `${fallback}_${index}`;
488
+ return isOperationPathAvailable([candidate], used, namespaces) ? candidate : next(index + 1);
489
+ };
490
+ return [next(2)];
491
+ };
492
+ const isOperationPathAvailable = (segments, used, namespaces) => {
493
+ const key = segments.join(".");
494
+ if (used.has(key) || namespaces.has(key))
495
+ return false;
496
+ return segments.slice(0, -1).every((_, index) => !used.has(segments.slice(0, index + 1).join(".")));
497
+ };
498
+ export const specServerUrl = (source) => {
499
+ const server = asArray(source.servers).find(isRecord);
500
+ const url = server === undefined ? undefined : nonEmptyString(server.url);
501
+ if (url === undefined)
502
+ return { ok: false, reason: "spec declares no servers; pass baseUrl" };
503
+ if (/\{[^{}]+\}/.test(url)) {
504
+ return { ok: false, reason: `server URL '${url}' is not an absolute URL; pass baseUrl` };
505
+ }
506
+ return validateBaseUrl(url);
507
+ };
508
+ export const validateBaseUrl = (value) => {
509
+ if (!/^https?:\/\//i.test(value))
510
+ return { ok: false, reason: `server URL '${value}' is not an absolute HTTP(S) URL` };
511
+ const url = URL.parse(value);
512
+ if (url === null || (url.protocol !== "http:" && url.protocol !== "https:")) {
513
+ return { ok: false, reason: `server URL '${value}' is not an absolute HTTP(S) URL` };
514
+ }
515
+ if (url.search !== "" || url.hash !== "") {
516
+ return { ok: false, reason: `server URL '${value}' contains an unsupported query string or fragment` };
517
+ }
518
+ return { ok: true, value };
519
+ };
520
+ export const securityRequirements = (value) => {
521
+ if (value === undefined)
522
+ return { ok: true, value: [] };
523
+ if (!Array.isArray(value))
524
+ return { ok: false, reason: "security declaration is not an array" };
525
+ const requirements = [];
526
+ for (const item of value) {
527
+ if (!isRecord(item))
528
+ return { ok: false, reason: "security requirement is not an object" };
529
+ const requirement = Object.create(null);
530
+ for (const [name, scopes] of Object.entries(item)) {
531
+ if (!Array.isArray(scopes))
532
+ return { ok: false, reason: "security requirement scopes are not string arrays" };
533
+ const parsed = scopes.filter((scope) => typeof scope === "string");
534
+ if (parsed.length !== scopes.length) {
535
+ return { ok: false, reason: "security requirement scopes are not string arrays" };
536
+ }
537
+ requirement[name] = parsed;
538
+ }
539
+ requirements.push(requirement);
540
+ }
541
+ return { ok: true, value: requirements };
542
+ };
543
+ export const operationSecurityRequirements = (value, defaults, schemes) => {
544
+ const parsed = value === undefined ? defaults : securityRequirements(value);
545
+ if (!parsed.ok)
546
+ return parsed;
547
+ const supported = parsed.value.filter((requirement) => Object.keys(requirement).every((name) => {
548
+ const scheme = own(schemes, name);
549
+ return scheme !== undefined && !(scheme.type === "apiKey" && scheme.in === "cookie");
550
+ }));
551
+ if (parsed.value.length === 0 || supported.length > 0)
552
+ return { ok: true, value: supported };
553
+ const names = [...new Set(parsed.value.flatMap((requirement) => Object.keys(requirement)))];
554
+ const cookieScheme = names.find((name) => {
555
+ const definition = own(schemes, name);
556
+ return definition?.type === "apiKey" && definition.in === "cookie";
557
+ });
558
+ return {
559
+ ok: false,
560
+ reason: cookieScheme === undefined
561
+ ? `security requirement references missing or malformed scheme: ${names.join(", ")}`
562
+ : `cookie authentication '${cookieScheme}' is not supported`,
563
+ };
564
+ };
565
+ export const securitySchemes = (document) => {
566
+ const components = isRecord(document.components) ? document.components : {};
567
+ const declared = isRecord(components.securitySchemes) ? components.securitySchemes : {};
568
+ return Object.fromEntries(Object.entries(declared).flatMap(([name, value]) => {
569
+ const resolved = resolve(document, value);
570
+ if (!isRecord(resolved))
571
+ return [];
572
+ const type = nonEmptyString(resolved.type);
573
+ if (type === "apiKey") {
574
+ const carrier = nonEmptyString(resolved.in);
575
+ const parameter = nonEmptyString(resolved.name);
576
+ if (parameter === undefined || (carrier !== "header" && carrier !== "query" && carrier !== "cookie"))
577
+ return [];
578
+ return [[name, { type, name: parameter, in: carrier }]];
579
+ }
580
+ if (type === "http") {
581
+ const scheme = nonEmptyString(resolved.scheme)?.toLowerCase();
582
+ return scheme === undefined ? [] : [[name, { type, scheme }]];
583
+ }
584
+ if (type === "oauth2" || type === "openIdConnect")
585
+ return [[name, { type }]];
586
+ return [];
587
+ }));
588
+ };