toolcraft-openapi 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -0
- package/dist/api-command.d.ts +7 -0
- package/dist/api-command.js +4 -0
- package/dist/auth/bearer-token-auth.d.ts +8 -0
- package/dist/auth/bearer-token-auth.js +216 -0
- package/dist/auth/types.d.ts +9 -0
- package/dist/auth/types.js +1 -0
- package/dist/bin/generate.d.ts +40 -0
- package/dist/bin/generate.js +248 -0
- package/dist/define-client.d.ts +20 -0
- package/dist/define-client.js +148 -0
- package/dist/generate.d.ts +210 -0
- package/dist/generate.js +1131 -0
- package/dist/group-by-noun.d.ts +6 -0
- package/dist/group-by-noun.js +17 -0
- package/dist/http.d.ts +26 -0
- package/dist/http.js +123 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +6 -0
- package/dist/interpreter.d.ts +6 -0
- package/dist/interpreter.js +289 -0
- package/dist/lock.d.ts +14 -0
- package/dist/lock.js +48 -0
- package/dist/naming.d.ts +24 -0
- package/dist/naming.js +218 -0
- package/dist/request-shape.d.ts +15 -0
- package/dist/request-shape.js +5 -0
- package/dist/runtime.d.ts +13 -0
- package/dist/runtime.js +94 -0
- package/dist/spec-source.d.ts +11 -0
- package/dist/spec-source.js +63 -0
- package/node_modules/@poe-code/design-system/dist/acp/components.d.ts +11 -0
- package/node_modules/@poe-code/design-system/dist/acp/components.js +121 -0
- package/node_modules/@poe-code/design-system/dist/acp/index.d.ts +3 -0
- package/node_modules/@poe-code/design-system/dist/acp/index.js +2 -0
- package/node_modules/@poe-code/design-system/dist/acp/writer.d.ts +13 -0
- package/node_modules/@poe-code/design-system/dist/acp/writer.js +21 -0
- package/node_modules/@poe-code/design-system/dist/components/command-errors.d.ts +16 -0
- package/node_modules/@poe-code/design-system/dist/components/command-errors.js +22 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter.d.ts +20 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter.js +27 -0
- package/node_modules/@poe-code/design-system/dist/components/index.d.ts +10 -0
- package/node_modules/@poe-code/design-system/dist/components/index.js +7 -0
- package/node_modules/@poe-code/design-system/dist/components/logger.d.ts +11 -0
- package/node_modules/@poe-code/design-system/dist/components/logger.js +60 -0
- package/node_modules/@poe-code/design-system/dist/components/symbols.d.ts +12 -0
- package/node_modules/@poe-code/design-system/dist/components/symbols.js +71 -0
- package/node_modules/@poe-code/design-system/dist/components/table.d.ts +13 -0
- package/node_modules/@poe-code/design-system/dist/components/table.js +74 -0
- package/node_modules/@poe-code/design-system/dist/components/text.d.ts +14 -0
- package/node_modules/@poe-code/design-system/dist/components/text.js +104 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/ansi.d.ts +18 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/ansi.js +298 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/buffer.d.ts +25 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +189 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/border.d.ts +9 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/border.js +123 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/footer.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/footer.js +57 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/output-pane.d.ts +12 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/output-pane.js +254 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/stats-pane.d.ts +7 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/stats-pane.js +121 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/dashboard.d.ts +20 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/dashboard.js +167 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/demo.d.ts +13 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/demo.js +145 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/index.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/index.js +4 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/keymap.d.ts +3 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/keymap.js +99 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/layout.d.ts +25 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/layout.js +79 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/should-use-dashboard.d.ts +10 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/should-use-dashboard.js +7 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/snapshot.d.ts +10 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/snapshot.js +68 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/store.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/store.js +51 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/terminal.d.ts +37 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/terminal.js +233 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +36 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/types.js +1 -0
- package/node_modules/@poe-code/design-system/dist/index.d.ts +33 -0
- package/node_modules/@poe-code/design-system/dist/index.js +31 -0
- package/node_modules/@poe-code/design-system/dist/internal/output-format.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/internal/output-format.js +22 -0
- package/node_modules/@poe-code/design-system/dist/internal/strip-ansi.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/internal/strip-ansi.js +3 -0
- package/node_modules/@poe-code/design-system/dist/internal/theme-detect.d.ts +11 -0
- package/node_modules/@poe-code/design-system/dist/internal/theme-detect.js +49 -0
- package/node_modules/@poe-code/design-system/dist/prompts/index.d.ts +66 -0
- package/node_modules/@poe-code/design-system/dist/prompts/index.js +132 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/cancel.d.ts +2 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/cancel.js +9 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +15 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.d.ts +18 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +101 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +39 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/outro.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/outro.js +16 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/spinner.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/spinner.js +74 -0
- package/node_modules/@poe-code/design-system/dist/prompts/theme.d.ts +11 -0
- package/node_modules/@poe-code/design-system/dist/prompts/theme.js +12 -0
- package/node_modules/@poe-code/design-system/dist/static/index.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/static/index.js +2 -0
- package/node_modules/@poe-code/design-system/dist/static/menu.d.ts +11 -0
- package/node_modules/@poe-code/design-system/dist/static/menu.js +36 -0
- package/node_modules/@poe-code/design-system/dist/static/spinner.d.ts +14 -0
- package/node_modules/@poe-code/design-system/dist/static/spinner.js +46 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/ast.d.ts +92 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/ast.js +1 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/demo-content.d.ts +2 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/demo-content.js +139 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/index.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/index.js +8 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/block.d.ts +7 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/block.js +1495 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/frontmatter.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/frontmatter.js +412 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/inline.d.ts +10 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/inline.js +1166 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser.d.ts +5 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser.js +42 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/renderer.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/renderer.js +572 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/testing/theme-render-fixture.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/testing/theme-render-fixture.js +27 -0
- package/node_modules/@poe-code/design-system/dist/tokens/colors.d.ts +35 -0
- package/node_modules/@poe-code/design-system/dist/tokens/colors.js +34 -0
- package/node_modules/@poe-code/design-system/dist/tokens/index.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/tokens/index.js +4 -0
- package/node_modules/@poe-code/design-system/dist/tokens/spacing.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/tokens/spacing.js +6 -0
- package/node_modules/@poe-code/design-system/dist/tokens/typography.d.ts +7 -0
- package/node_modules/@poe-code/design-system/dist/tokens/typography.js +8 -0
- package/node_modules/@poe-code/design-system/dist/tokens/widths.d.ts +5 -0
- package/node_modules/@poe-code/design-system/dist/tokens/widths.js +5 -0
- package/node_modules/@poe-code/design-system/package.json +25 -0
- package/node_modules/auth-store/README.md +47 -0
- package/node_modules/auth-store/dist/create-secret-store.d.ts +2 -0
- package/node_modules/auth-store/dist/create-secret-store.js +35 -0
- package/node_modules/auth-store/dist/encrypted-file-store.d.ts +39 -0
- package/node_modules/auth-store/dist/encrypted-file-store.js +156 -0
- package/node_modules/auth-store/dist/index.d.ts +7 -0
- package/node_modules/auth-store/dist/index.js +4 -0
- package/node_modules/auth-store/dist/keychain-store.d.ts +22 -0
- package/node_modules/auth-store/dist/keychain-store.js +111 -0
- package/node_modules/auth-store/dist/provider-store.d.ts +10 -0
- package/node_modules/auth-store/dist/provider-store.js +28 -0
- package/node_modules/auth-store/dist/types.d.ts +20 -0
- package/node_modules/auth-store/dist/types.js +1 -0
- package/node_modules/auth-store/package.json +25 -0
- package/package.json +48 -0
package/dist/generate.js
ADDED
|
@@ -0,0 +1,1131 @@
|
|
|
1
|
+
import { UserError } from "agent-kit";
|
|
2
|
+
import { METHOD_DEFAULTS, deriveNoun, deriveVerb, isIdentifierName, normalizeParamName, toCamelCase, toPascalCase } from "./naming.js";
|
|
3
|
+
import { groupByNoun } from "./group-by-noun.js";
|
|
4
|
+
import { renderPreflightBlock, renderRequestShape } from "./interpreter.js";
|
|
5
|
+
const HTTP_METHOD_ORDER = ["get", "post", "put", "patch", "delete"];
|
|
6
|
+
const UNSUPPORTED_HTTP_METHODS = ["head", "options", "trace"];
|
|
7
|
+
const METHODS_WITHOUT_REQUEST_BODY = new Set(["get"]);
|
|
8
|
+
const SCHEMA_TYPE_TO_KIND = {
|
|
9
|
+
boolean: { kind: "boolean" },
|
|
10
|
+
integer: { kind: "number", jsonType: "integer" },
|
|
11
|
+
number: { kind: "number" },
|
|
12
|
+
string: { kind: "string" }
|
|
13
|
+
};
|
|
14
|
+
const NULL_HELPER_SUPPORT = {
|
|
15
|
+
body: { array: true, scalar: true },
|
|
16
|
+
path: { array: false, scalar: false },
|
|
17
|
+
// Query null already serializes as the existing empty-string wire encoding, so v1
|
|
18
|
+
// keeps null-helper flags body-only until a real query-null convention lands.
|
|
19
|
+
query: { array: false, scalar: false }
|
|
20
|
+
};
|
|
21
|
+
const TRANSPORT_PARAMS = [
|
|
22
|
+
{
|
|
23
|
+
paramName: "dryRun",
|
|
24
|
+
sourceName: "dryRun",
|
|
25
|
+
location: "transport",
|
|
26
|
+
description: "Print the HTTP request and exit without sending it.",
|
|
27
|
+
scope: ["cli", "sdk"],
|
|
28
|
+
optional: true,
|
|
29
|
+
definition: { kind: "boolean" }
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
paramName: "verbose",
|
|
33
|
+
sourceName: "verbose",
|
|
34
|
+
location: "transport",
|
|
35
|
+
description: "Log the request line to stderr.",
|
|
36
|
+
shortFlag: "v",
|
|
37
|
+
scope: ["cli", "sdk"],
|
|
38
|
+
optional: true,
|
|
39
|
+
definition: { kind: "boolean" }
|
|
40
|
+
}
|
|
41
|
+
];
|
|
42
|
+
const SCHEMA_OPTION_SOURCES = [
|
|
43
|
+
{
|
|
44
|
+
key: "description",
|
|
45
|
+
get: (param) => param.description
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
key: "default",
|
|
49
|
+
get: (param) => param.definition.defaultValue
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: "short",
|
|
53
|
+
get: (param) => param.shortFlag
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
key: "scope",
|
|
57
|
+
get: (param) => param.scope
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: "minimum",
|
|
61
|
+
get: (param) => param.definition.minimum
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: "maximum",
|
|
65
|
+
get: (param) => param.definition.maximum
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
key: "minLength",
|
|
69
|
+
get: (param) => param.definition.minLength
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: "maxLength",
|
|
73
|
+
get: (param) => param.definition.maxLength
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
key: "minItems",
|
|
77
|
+
get: (param) => param.definition.minItems
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: "maxItems",
|
|
81
|
+
get: (param) => param.definition.maxItems
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
key: "pattern",
|
|
85
|
+
get: (param) => param.definition.pattern
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
key: "format",
|
|
89
|
+
get: (param) => param.definition.format
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
key: "jsonType",
|
|
93
|
+
get: (param) => param.definition.jsonType
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
key: "nullable",
|
|
97
|
+
get: (param) => param.definition.nullable === true ? true : undefined
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
key: "requiredScopes",
|
|
101
|
+
get: (param) => param.definition.requiredScopes
|
|
102
|
+
}
|
|
103
|
+
];
|
|
104
|
+
export function generate(document, options) {
|
|
105
|
+
const commands = collectGeneratedCommands(document);
|
|
106
|
+
return [
|
|
107
|
+
...commands.map((command) => ({
|
|
108
|
+
path: command.filePath,
|
|
109
|
+
contents: createCommandFile({
|
|
110
|
+
specSha: options.specSha,
|
|
111
|
+
...command
|
|
112
|
+
})
|
|
113
|
+
})),
|
|
114
|
+
createIndexFile(commands)
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
export function collectGeneratedCommands(document) {
|
|
118
|
+
const paths = document.paths;
|
|
119
|
+
if (paths === undefined) {
|
|
120
|
+
throw new UserError('OpenAPI document must define a top-level "paths" object.');
|
|
121
|
+
}
|
|
122
|
+
const commands = collectOperations(paths).map((entry) => createGeneratedCommand(document, entry));
|
|
123
|
+
assertUniqueCommandPaths(commands);
|
|
124
|
+
return commands.slice().sort((left, right) => compareGeneratedCommandPaths(left, right));
|
|
125
|
+
}
|
|
126
|
+
function collectOperations(paths) {
|
|
127
|
+
return Object.entries(paths)
|
|
128
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
129
|
+
.flatMap(([path, pathItem]) => {
|
|
130
|
+
if (pathItem === undefined) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
assertSupportedHttpMethods(path, pathItem);
|
|
134
|
+
return HTTP_METHOD_ORDER.flatMap((method) => {
|
|
135
|
+
const operation = pathItem[method];
|
|
136
|
+
if (operation === undefined) {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
return [{ method, path, operation, pathItem }];
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function createGeneratedCommand(document, entry) {
|
|
144
|
+
const operation = expectOperation(document, entry.operation, entry.method, entry.path);
|
|
145
|
+
const operationId = operation.operationId ?? `${entry.method.toUpperCase()} ${entry.path}`;
|
|
146
|
+
assertSupportedOperationMetadata(operation, operationId);
|
|
147
|
+
assertSupportedSuccessResponses(document, operation, operationId);
|
|
148
|
+
const noun = deriveNoun(operation, entry.path, operationId);
|
|
149
|
+
assertValidGeneratedNoun(operationId, noun);
|
|
150
|
+
const verb = deriveVerb(entry.method, entry.path, operation, operationId, noun);
|
|
151
|
+
const collected = collectParams(document, entry, operation, operationId);
|
|
152
|
+
const methodDefaults = METHOD_DEFAULTS[entry.method];
|
|
153
|
+
const exportName = `${toCamelCase(noun)}${toPascalCase(verb)}Command`;
|
|
154
|
+
const filePath = `${noun}/${verb}.ts`;
|
|
155
|
+
return {
|
|
156
|
+
noun,
|
|
157
|
+
verb,
|
|
158
|
+
exportName,
|
|
159
|
+
filePath,
|
|
160
|
+
operationId,
|
|
161
|
+
description: mergeCommandDescriptions(operation.description ?? operation.summary, collected.requestBodyDescription),
|
|
162
|
+
method: entry.method.toUpperCase(),
|
|
163
|
+
path: entry.path,
|
|
164
|
+
auth: getOperationAuthMode(document, operation, operationId),
|
|
165
|
+
confirm: methodDefaults?.confirm === true,
|
|
166
|
+
params: collected.params,
|
|
167
|
+
paramsSchemaOptions: collected.paramsSchemaOptions,
|
|
168
|
+
preflightBlocks: collected.preflightBlocks,
|
|
169
|
+
requestFields: collected.requestFields,
|
|
170
|
+
sectionRenders: collected.sectionRenders,
|
|
171
|
+
optionalSections: collected.optionalSections
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function assertSupportedOperationMetadata(operation, operationId) {
|
|
175
|
+
if (operation.servers !== undefined) {
|
|
176
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported per-operation servers. Configure the client baseUrl instead.`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function collectParams(document, entry, operation, operationId) {
|
|
180
|
+
const operationParams = collectOperationParameters(document, entry.path, entry.pathItem.parameters ?? [], operation.parameters ?? [], operationId);
|
|
181
|
+
const requestBodyParams = collectRequestBodyParams(document, operation, operationId, entry.method);
|
|
182
|
+
const params = [...operationParams.params, ...requestBodyParams.params, ...TRANSPORT_PARAMS];
|
|
183
|
+
const deduped = new Map();
|
|
184
|
+
for (const param of params) {
|
|
185
|
+
const existing = deduped.get(param.paramName);
|
|
186
|
+
if (existing !== undefined) {
|
|
187
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} maps both ${JSON.stringify(existing.sourceName)} and ${JSON.stringify(param.sourceName)} to flag ${JSON.stringify(param.paramName)}.`);
|
|
188
|
+
}
|
|
189
|
+
deduped.set(param.paramName, param);
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
params: [...deduped.values()],
|
|
193
|
+
paramsSchemaOptions: requestBodyParams.paramsSchemaOptions,
|
|
194
|
+
preflightBlocks: [...operationParams.preflightBlocks, ...requestBodyParams.preflightBlocks],
|
|
195
|
+
requestFields: [...operationParams.requestFields, ...requestBodyParams.requestFields],
|
|
196
|
+
sectionRenders: { ...operationParams.sectionRenders, ...requestBodyParams.sectionRenders },
|
|
197
|
+
optionalSections: new Set([
|
|
198
|
+
...operationParams.optionalSections,
|
|
199
|
+
...requestBodyParams.optionalSections
|
|
200
|
+
]),
|
|
201
|
+
requestBodyDescription: requestBodyParams.requestBodyDescription
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function collectOperationParameters(document, path, pathItemParameters, operationParameters, operationId) {
|
|
205
|
+
const merged = new Map();
|
|
206
|
+
for (const parameter of pathItemParameters) {
|
|
207
|
+
const resolved = expectParameter(document, parameter, operationId);
|
|
208
|
+
merged.set(`${resolved.in}:${resolved.name}`, resolved);
|
|
209
|
+
}
|
|
210
|
+
for (const parameter of operationParameters) {
|
|
211
|
+
const resolved = expectParameter(document, parameter, operationId);
|
|
212
|
+
merged.set(`${resolved.in}:${resolved.name}`, resolved);
|
|
213
|
+
}
|
|
214
|
+
assertPathTemplateParameters(path, merged, operationId);
|
|
215
|
+
const params = [];
|
|
216
|
+
const preflightBlocks = [];
|
|
217
|
+
const requestFields = [];
|
|
218
|
+
for (const parameter of merged.values()) {
|
|
219
|
+
const generated = createGeneratedParameter(document, parameter, operationId);
|
|
220
|
+
params.push(...generated.params);
|
|
221
|
+
preflightBlocks.push(...generated.preflightBlocks);
|
|
222
|
+
requestFields.push(generated.requestField);
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
params,
|
|
226
|
+
paramsSchemaOptions: undefined,
|
|
227
|
+
preflightBlocks,
|
|
228
|
+
requestFields,
|
|
229
|
+
sectionRenders: { path: "wrapped", query: "wrapped" },
|
|
230
|
+
optionalSections: new Set(),
|
|
231
|
+
requestBodyDescription: undefined
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function collectRequestBodyParams(document, operation, operationId, method) {
|
|
235
|
+
if (operation.requestBody === undefined) {
|
|
236
|
+
return {
|
|
237
|
+
params: [],
|
|
238
|
+
paramsSchemaOptions: undefined,
|
|
239
|
+
preflightBlocks: [],
|
|
240
|
+
requestFields: [],
|
|
241
|
+
sectionRenders: {},
|
|
242
|
+
optionalSections: new Set(),
|
|
243
|
+
requestBodyDescription: undefined
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
if (METHODS_WITHOUT_REQUEST_BODY.has(method)) {
|
|
247
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported requestBody on ${method.toUpperCase()}. Request bodies are not supported on GET in v1.`);
|
|
248
|
+
}
|
|
249
|
+
const requestBody = expectRequestBody(document, operation.requestBody, operationId, "requestBody");
|
|
250
|
+
const content = Object.entries(requestBody.content ?? {}).find(([mediaType, mediaTypeObject]) => mediaTypeObject !== undefined && isJsonMediaType(mediaType))?.[1];
|
|
251
|
+
if (content === undefined) {
|
|
252
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} must define a JSON request body media type in v1.`);
|
|
253
|
+
}
|
|
254
|
+
const schema = expectSchema(document, content.schema, operationId, "requestBody");
|
|
255
|
+
const bodyOptional = requestBody.required !== true;
|
|
256
|
+
if (schema.type !== "object") {
|
|
257
|
+
const bodySchema = schema.description === undefined && requestBody.description !== undefined
|
|
258
|
+
? { ...schema, description: requestBody.description }
|
|
259
|
+
: schema;
|
|
260
|
+
return createCollectedRequestBodyParams([createBodyField(document, "body", bodySchema, bodyOptional, operationId)], bodyOptional, schema.description === undefined ? undefined : requestBody.description, "inline", undefined);
|
|
261
|
+
}
|
|
262
|
+
if (schema.additionalProperties !== undefined && schema.additionalProperties !== false) {
|
|
263
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported requestBody. Object request bodies with additionalProperties are not supported in v1.`);
|
|
264
|
+
}
|
|
265
|
+
if (schema.properties === undefined) {
|
|
266
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} must define an object-shaped JSON request body.`);
|
|
267
|
+
}
|
|
268
|
+
const required = new Set(schema.required ?? []);
|
|
269
|
+
const assemblies = [];
|
|
270
|
+
const declaredPropertyCount = Object.keys(schema.properties).length;
|
|
271
|
+
for (const [name, property] of Object.entries(schema.properties)) {
|
|
272
|
+
const propertySchema = expectSchema(document, property, operationId, `requestBody.properties.${name}`);
|
|
273
|
+
if (propertySchema.readOnly === true) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
const generated = createBodyField(document, name, propertySchema, bodyOptional || !required.has(name), operationId);
|
|
277
|
+
assemblies.push(generated);
|
|
278
|
+
}
|
|
279
|
+
if (!bodyOptional && assemblies.length === 0) {
|
|
280
|
+
const reason = declaredPropertyCount === 0
|
|
281
|
+
? "does not define any writable fields"
|
|
282
|
+
: "all declared fields are readOnly";
|
|
283
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} requestBody is required but ${reason}.`);
|
|
284
|
+
}
|
|
285
|
+
return createCollectedRequestBodyParams(assemblies, bodyOptional, requestBody.description, "wrapped", schema.additionalProperties === false ? { additionalProperties: false } : undefined);
|
|
286
|
+
}
|
|
287
|
+
function assertSupportedSuccessResponses(document, operation, operationId) {
|
|
288
|
+
for (const [statusCode, response] of Object.entries(operation.responses ?? {})) {
|
|
289
|
+
if (!isSuccessStatusCode(statusCode)) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const resolvedResponse = expectResponse(document, response, operationId, statusCode);
|
|
293
|
+
const declaredMediaTypes = Object.entries(resolvedResponse.content ?? {})
|
|
294
|
+
.filter(([, mediaType]) => mediaType !== undefined)
|
|
295
|
+
.map(([mediaType]) => mediaType);
|
|
296
|
+
if (declaredMediaTypes.length === 0 ||
|
|
297
|
+
declaredMediaTypes.every((mediaType) => isJsonMediaType(mediaType))) {
|
|
298
|
+
for (const [mediaType, mediaTypeObject] of Object.entries(resolvedResponse.content ?? {})) {
|
|
299
|
+
if (mediaTypeObject === undefined ||
|
|
300
|
+
mediaTypeObject.schema === undefined ||
|
|
301
|
+
!isJsonMediaType(mediaType)) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
assertSupportedSuccessResponseSchema(document, mediaTypeObject.schema, operationId, `success response schema for status ${JSON.stringify(statusCode)}`);
|
|
305
|
+
}
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} declares unsupported success response content type(s) for status ${JSON.stringify(statusCode)}: ${declaredMediaTypes
|
|
309
|
+
.map((mediaType) => JSON.stringify(mediaType))
|
|
310
|
+
.join(", ")}. Only application/json responses (or empty success responses) are supported in v1.`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function assertSupportedSuccessResponseSchema(document, schema, operationId, context) {
|
|
314
|
+
const resolvedSchema = expectSupportedSuccessResponseSchema(document, schema, operationId, context);
|
|
315
|
+
if (resolvedSchema.additionalProperties !== undefined &&
|
|
316
|
+
resolvedSchema.additionalProperties !== false) {
|
|
317
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. Object response schemas with additionalProperties are not supported in v1.`);
|
|
318
|
+
}
|
|
319
|
+
for (const [propertyName, propertySchema] of Object.entries(resolvedSchema.properties ?? {})) {
|
|
320
|
+
assertSupportedSuccessResponseSchema(document, propertySchema, operationId, `${context} property ${JSON.stringify(propertyName)}`);
|
|
321
|
+
}
|
|
322
|
+
if (resolvedSchema.items !== undefined) {
|
|
323
|
+
assertSupportedSuccessResponseSchema(document, resolvedSchema.items, operationId, `${context} items`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function expectSupportedSuccessResponseSchema(document, schema, operationId, context) {
|
|
327
|
+
const resolvedSchema = resolveSchema(document, schema, operationId, context);
|
|
328
|
+
const compositionKeyword = getCompositionKeyword(resolvedSchema);
|
|
329
|
+
if (compositionKeyword === undefined) {
|
|
330
|
+
return resolvedSchema;
|
|
331
|
+
}
|
|
332
|
+
const nullableAnyOfSchema = compositionKeyword === "anyOf"
|
|
333
|
+
? resolveNullableAnyOfSchema(document, resolvedSchema, operationId, context)
|
|
334
|
+
: undefined;
|
|
335
|
+
if (nullableAnyOfSchema !== undefined) {
|
|
336
|
+
return nullableAnyOfSchema;
|
|
337
|
+
}
|
|
338
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. JSON Schema composition keyword ${JSON.stringify(compositionKeyword)} is not supported in v1.`);
|
|
339
|
+
}
|
|
340
|
+
function createGeneratedParameter(document, parameter, operationId) {
|
|
341
|
+
const schema = expectSchema(document, parameter.schema, operationId, `parameter ${JSON.stringify(parameter.name)}`);
|
|
342
|
+
if (parameter.in === "path") {
|
|
343
|
+
if (parameter.required !== true) {
|
|
344
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} path parameter ${JSON.stringify(parameter.name)} must set required: true.`);
|
|
345
|
+
}
|
|
346
|
+
if (schema.nullable === true) {
|
|
347
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} path parameter ${JSON.stringify(parameter.name)} uses unsupported nullable schema. Path parameters cannot be nullable in v1.`);
|
|
348
|
+
}
|
|
349
|
+
assertSupportedPathParameterSerialization(parameter, operationId);
|
|
350
|
+
}
|
|
351
|
+
return createField({
|
|
352
|
+
document,
|
|
353
|
+
name: parameter.name,
|
|
354
|
+
description: parameter.description ?? schema.description,
|
|
355
|
+
schema,
|
|
356
|
+
optional: parameter.required !== true,
|
|
357
|
+
operationId,
|
|
358
|
+
context: `parameter ${JSON.stringify(parameter.name)}`,
|
|
359
|
+
location: parameter.in,
|
|
360
|
+
querySerialization: parameter.in === "query" && schema.type === "array"
|
|
361
|
+
? resolveQueryArraySerialization(parameter, operationId)
|
|
362
|
+
: undefined
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
function assertSupportedPathParameterSerialization(parameter, operationId) {
|
|
366
|
+
const style = parameter.style ?? "simple";
|
|
367
|
+
const explode = parameter.explode ?? false;
|
|
368
|
+
if (style === "simple" && explode === false) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} path parameter ${JSON.stringify(parameter.name)} uses unsupported serialization. Path parameters must use style "simple" with explode false in v1.`);
|
|
372
|
+
}
|
|
373
|
+
function createBodyField(document, name, schema, optional, operationId) {
|
|
374
|
+
return createField({
|
|
375
|
+
document,
|
|
376
|
+
name,
|
|
377
|
+
description: schema.description,
|
|
378
|
+
schema,
|
|
379
|
+
optional,
|
|
380
|
+
operationId,
|
|
381
|
+
context: `request body field ${JSON.stringify(name)}`,
|
|
382
|
+
location: "body"
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
function createField(options) {
|
|
386
|
+
return FIELD_ASSEMBLERS[options.location][getFieldSchemaKind(options.schema)](options);
|
|
387
|
+
}
|
|
388
|
+
function createScalarParam(options) {
|
|
389
|
+
const { document, name, description, schema, optional, operationId, context, location } = options;
|
|
390
|
+
const paramName = name;
|
|
391
|
+
const definition = createParamDefinition(document, schema, operationId, context);
|
|
392
|
+
const emitsNullHelper = supportsNullHelper(location, "scalar", definition.nullable);
|
|
393
|
+
const params = [
|
|
394
|
+
{
|
|
395
|
+
paramName,
|
|
396
|
+
sourceName: name,
|
|
397
|
+
location,
|
|
398
|
+
description,
|
|
399
|
+
optional: optional || emitsNullHelper,
|
|
400
|
+
definition: {
|
|
401
|
+
...definition,
|
|
402
|
+
...(!optional && emitsNullHelper ? { requiredScopes: ["mcp", "sdk"] } : {})
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
];
|
|
406
|
+
const preflightBlocks = [];
|
|
407
|
+
const helperBaseName = normalizeParamName(name);
|
|
408
|
+
const resolvedName = `resolved${toPascalCase(helperBaseName)}`;
|
|
409
|
+
if (emitsNullHelper) {
|
|
410
|
+
params.push({
|
|
411
|
+
paramName: `${helperBaseName}Null`,
|
|
412
|
+
sourceName: name,
|
|
413
|
+
location: "transport",
|
|
414
|
+
description: `Send null for ${name}.`,
|
|
415
|
+
optional: true,
|
|
416
|
+
scope: ["cli"],
|
|
417
|
+
definition: { kind: "boolean" }
|
|
418
|
+
});
|
|
419
|
+
preflightBlocks.push({
|
|
420
|
+
kind: "scalar-null",
|
|
421
|
+
paramName,
|
|
422
|
+
nullParamName: `${helperBaseName}Null`,
|
|
423
|
+
resolvedName,
|
|
424
|
+
required: !optional
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
params,
|
|
429
|
+
preflightBlocks,
|
|
430
|
+
requestField: {
|
|
431
|
+
location,
|
|
432
|
+
wireName: name,
|
|
433
|
+
value: emitsNullHelper
|
|
434
|
+
? { kind: "reference", reference: { kind: "resolved", resolvedName } }
|
|
435
|
+
: { kind: "reference", reference: { kind: "param", paramName } },
|
|
436
|
+
omitWhenUndefinedReference: emitsNullHelper
|
|
437
|
+
? { kind: "resolved", resolvedName }
|
|
438
|
+
: { kind: "param", paramName }
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function getFieldSchemaKind(schema) {
|
|
443
|
+
return schema.type === "array" ? "array" : schema.type === "object" ? "object" : "scalar";
|
|
444
|
+
}
|
|
445
|
+
function createPathScalarOnlyError(name, operationId) {
|
|
446
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} path parameter ${JSON.stringify(name)} must use a scalar schema (string, number, integer, or boolean).`);
|
|
447
|
+
}
|
|
448
|
+
function createUnsupportedNestedBodyFieldError(name, operationId) {
|
|
449
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported request body field ${JSON.stringify(name)}. Nested object body fields are not supported in v1.`);
|
|
450
|
+
}
|
|
451
|
+
function createArrayParam(options) {
|
|
452
|
+
const { document, name, description, schema, optional, operationId, context, location } = options;
|
|
453
|
+
const paramName = name;
|
|
454
|
+
const helperBaseName = normalizeParamName(name);
|
|
455
|
+
const directDefinition = createParamDefinition(document, location === "query" ? stripNullable(schema) : schema, operationId, context);
|
|
456
|
+
const jsonParamName = `${helperBaseName}Json`;
|
|
457
|
+
const nullParamName = `${helperBaseName}Null`;
|
|
458
|
+
const resolvedName = `resolved${toPascalCase(helperBaseName)}`;
|
|
459
|
+
const reference = { kind: "resolved", resolvedName };
|
|
460
|
+
const emitsNullHelper = supportsNullHelper(location, "array", directDefinition.nullable);
|
|
461
|
+
const directParamOptional = true;
|
|
462
|
+
const params = [
|
|
463
|
+
{
|
|
464
|
+
paramName,
|
|
465
|
+
sourceName: name,
|
|
466
|
+
location,
|
|
467
|
+
description,
|
|
468
|
+
optional: directParamOptional,
|
|
469
|
+
definition: {
|
|
470
|
+
...directDefinition,
|
|
471
|
+
...(optional ? {} : { requiredScopes: ["mcp", "sdk"] })
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
paramName: jsonParamName,
|
|
476
|
+
sourceName: name,
|
|
477
|
+
location: "transport",
|
|
478
|
+
description: `JSON-encoded value for ${name}.`,
|
|
479
|
+
optional: true,
|
|
480
|
+
scope: ["cli"],
|
|
481
|
+
definition: { kind: "string" }
|
|
482
|
+
}
|
|
483
|
+
];
|
|
484
|
+
if (emitsNullHelper) {
|
|
485
|
+
params.push({
|
|
486
|
+
paramName: nullParamName,
|
|
487
|
+
sourceName: name,
|
|
488
|
+
location: "transport",
|
|
489
|
+
description: `Send null for ${name}.`,
|
|
490
|
+
optional: true,
|
|
491
|
+
scope: ["cli"],
|
|
492
|
+
definition: { kind: "boolean" }
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
return {
|
|
496
|
+
params,
|
|
497
|
+
preflightBlocks: [
|
|
498
|
+
{
|
|
499
|
+
kind: "array",
|
|
500
|
+
paramName,
|
|
501
|
+
jsonParamName,
|
|
502
|
+
...(emitsNullHelper ? { nullParamName } : {}),
|
|
503
|
+
resolvedName,
|
|
504
|
+
required: !optional
|
|
505
|
+
}
|
|
506
|
+
],
|
|
507
|
+
requestField: {
|
|
508
|
+
location,
|
|
509
|
+
wireName: name,
|
|
510
|
+
value: ARRAY_VALUE_EXPRESSIONS[location](reference, options.querySerialization),
|
|
511
|
+
omitWhenUndefinedReference: reference
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
const ARRAY_VALUE_EXPRESSIONS = {
|
|
516
|
+
body: (reference) => ({ kind: "reference", reference }),
|
|
517
|
+
query: (reference, querySerialization) => ({
|
|
518
|
+
kind: "queryArray",
|
|
519
|
+
reference,
|
|
520
|
+
serialization: expectQueryArraySerialization(querySerialization)
|
|
521
|
+
})
|
|
522
|
+
};
|
|
523
|
+
const FIELD_ASSEMBLERS = {
|
|
524
|
+
body: {
|
|
525
|
+
array: (options) => createArrayParam({
|
|
526
|
+
...options,
|
|
527
|
+
location: "body"
|
|
528
|
+
}),
|
|
529
|
+
object: (options) => createUnsupportedNestedBodyFieldError(options.name, options.operationId),
|
|
530
|
+
scalar: (options) => createScalarParam({
|
|
531
|
+
...options,
|
|
532
|
+
location: "body"
|
|
533
|
+
})
|
|
534
|
+
},
|
|
535
|
+
path: {
|
|
536
|
+
array: (options) => createPathScalarOnlyError(options.name, options.operationId),
|
|
537
|
+
object: (options) => createPathScalarOnlyError(options.name, options.operationId),
|
|
538
|
+
scalar: (options) => createScalarParam({
|
|
539
|
+
...options,
|
|
540
|
+
location: "path"
|
|
541
|
+
})
|
|
542
|
+
},
|
|
543
|
+
query: {
|
|
544
|
+
array: (options) => createArrayParam({
|
|
545
|
+
...options,
|
|
546
|
+
location: "query"
|
|
547
|
+
}),
|
|
548
|
+
object: (options) => createScalarParam({
|
|
549
|
+
...options,
|
|
550
|
+
location: "query"
|
|
551
|
+
}),
|
|
552
|
+
scalar: (options) => createScalarParam({
|
|
553
|
+
...options,
|
|
554
|
+
location: "query"
|
|
555
|
+
})
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
function expectQueryArraySerialization(querySerialization) {
|
|
559
|
+
if (querySerialization === undefined) {
|
|
560
|
+
throw new Error("Missing query array serialization for generated query array field.");
|
|
561
|
+
}
|
|
562
|
+
return querySerialization;
|
|
563
|
+
}
|
|
564
|
+
function supportsNullHelper(location, shape, nullable) {
|
|
565
|
+
return nullable === true && NULL_HELPER_SUPPORT[location][shape];
|
|
566
|
+
}
|
|
567
|
+
function createCollectedRequestBodyParams(assemblies, bodyOptional, requestBodyDescription, bodyRender, paramsSchemaOptions) {
|
|
568
|
+
return {
|
|
569
|
+
params: assemblies.flatMap((assembly) => assembly.params),
|
|
570
|
+
paramsSchemaOptions,
|
|
571
|
+
preflightBlocks: assemblies.flatMap((assembly) => assembly.preflightBlocks),
|
|
572
|
+
requestFields: assemblies.map((assembly) => assembly.requestField),
|
|
573
|
+
sectionRenders: { body: bodyRender },
|
|
574
|
+
optionalSections: bodyOptional
|
|
575
|
+
? new Set(["body"])
|
|
576
|
+
: new Set(),
|
|
577
|
+
requestBodyDescription
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function createParamDefinition(document, schema, operationId, context) {
|
|
581
|
+
if (schema.type === "array") {
|
|
582
|
+
const itemSchema = expectArrayItemsSchema(document, schema, operationId, context);
|
|
583
|
+
return {
|
|
584
|
+
kind: "array",
|
|
585
|
+
itemDefinition: createParamDefinition(document, itemSchema, operationId, `${context} items`),
|
|
586
|
+
...(schema.default === undefined ? {} : { defaultValue: schema.default }),
|
|
587
|
+
...(schema.minItems === undefined ? {} : { minItems: schema.minItems }),
|
|
588
|
+
...(schema.maxItems === undefined ? {} : { maxItems: schema.maxItems }),
|
|
589
|
+
...(schema.nullable === true ? { nullable: true } : {})
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
const scalarDefinition = schema.type === undefined || !(schema.type in SCHEMA_TYPE_TO_KIND)
|
|
593
|
+
? undefined
|
|
594
|
+
: SCHEMA_TYPE_TO_KIND[schema.type];
|
|
595
|
+
const enumValues = normalizeEnumValues(schema.enum, operationId, context, schema.nullable === true, schema.type);
|
|
596
|
+
if (enumValues !== undefined) {
|
|
597
|
+
return {
|
|
598
|
+
kind: "enum",
|
|
599
|
+
enumValues,
|
|
600
|
+
...(scalarDefinition?.jsonType === undefined ? {} : { jsonType: scalarDefinition.jsonType }),
|
|
601
|
+
...(schema.default === undefined ? {} : { defaultValue: schema.default }),
|
|
602
|
+
...(schema.nullable === true || schema.enum?.includes(null) === true
|
|
603
|
+
? { nullable: true }
|
|
604
|
+
: {})
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
if (scalarDefinition !== undefined) {
|
|
608
|
+
return {
|
|
609
|
+
kind: scalarDefinition.kind,
|
|
610
|
+
...(scalarDefinition.jsonType === undefined ? {} : { jsonType: scalarDefinition.jsonType }),
|
|
611
|
+
...(schema.default === undefined ? {} : { defaultValue: schema.default }),
|
|
612
|
+
...(schema.minimum === undefined ? {} : { minimum: schema.minimum }),
|
|
613
|
+
...(schema.maximum === undefined ? {} : { maximum: schema.maximum }),
|
|
614
|
+
...(schema.minLength === undefined ? {} : { minLength: schema.minLength }),
|
|
615
|
+
...(schema.maxLength === undefined ? {} : { maxLength: schema.maxLength }),
|
|
616
|
+
...(schema.pattern === undefined ? {} : { pattern: schema.pattern }),
|
|
617
|
+
...(schema.format === undefined ? {} : { format: schema.format }),
|
|
618
|
+
...(schema.nullable === true ? { nullable: true } : {})
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. Supported shapes in this milestone are string, number, integer, boolean, enum, and arrays of those values.`);
|
|
622
|
+
}
|
|
623
|
+
function assertPathTemplateParameters(path, parameters, operationId) {
|
|
624
|
+
const placeholders = new Set(collectPathPlaceholders(path));
|
|
625
|
+
for (const placeholder of placeholders) {
|
|
626
|
+
if (!parameters.has(`path:${placeholder}`)) {
|
|
627
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} path ${JSON.stringify(path)} references ${JSON.stringify(`{${placeholder}}`)} but does not define a matching path parameter.`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
for (const parameter of parameters.values()) {
|
|
631
|
+
if (parameter.in !== "path") {
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (!placeholders.has(parameter.name)) {
|
|
635
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} path ${JSON.stringify(path)} declares path parameter ${JSON.stringify(parameter.name)} but the path template does not include ${JSON.stringify(`{${parameter.name}}`)}.`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function collectPathPlaceholders(path) {
|
|
640
|
+
const placeholders = [];
|
|
641
|
+
let searchFrom = 0;
|
|
642
|
+
while (searchFrom < path.length) {
|
|
643
|
+
const start = path.indexOf("{", searchFrom);
|
|
644
|
+
if (start === -1) {
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
const end = path.indexOf("}", start + 1);
|
|
648
|
+
if (end === -1) {
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
placeholders.push(path.slice(start + 1, end));
|
|
652
|
+
searchFrom = end + 1;
|
|
653
|
+
}
|
|
654
|
+
return placeholders;
|
|
655
|
+
}
|
|
656
|
+
function isSuccessStatusCode(statusCode) {
|
|
657
|
+
if (statusCode === "default" || statusCode === "2XX") {
|
|
658
|
+
return true;
|
|
659
|
+
}
|
|
660
|
+
return (statusCode.length === 3 &&
|
|
661
|
+
statusCode[0] === "2" &&
|
|
662
|
+
isAsciiDigit(statusCode[1]) &&
|
|
663
|
+
isAsciiDigit(statusCode[2]));
|
|
664
|
+
}
|
|
665
|
+
function isAsciiDigit(character) {
|
|
666
|
+
return character >= "0" && character <= "9";
|
|
667
|
+
}
|
|
668
|
+
function isJsonMediaType(mediaType) {
|
|
669
|
+
const normalized = mediaType.toLowerCase();
|
|
670
|
+
return normalized.includes("application/json") || normalized.includes("+json");
|
|
671
|
+
}
|
|
672
|
+
function normalizeEnumValues(enumValues, operationId, context, nullable, schemaType) {
|
|
673
|
+
if (enumValues === undefined) {
|
|
674
|
+
return undefined;
|
|
675
|
+
}
|
|
676
|
+
const filteredValues = enumValues.filter((value) => value !== null);
|
|
677
|
+
if (enumValues.includes(null) && nullable !== true) {
|
|
678
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. Enums may include null only when the schema is marked nullable.`);
|
|
679
|
+
}
|
|
680
|
+
if (filteredValues.length === 0) {
|
|
681
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. Enum values cannot be empty.`);
|
|
682
|
+
}
|
|
683
|
+
if (filteredValues.some((value) => !isEnumPrimitiveValue(value))) {
|
|
684
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. OpenAPI enums must contain only string, number, boolean, or null values.`);
|
|
685
|
+
}
|
|
686
|
+
const primitiveTypes = new Set(filteredValues.map((value) => typeof value));
|
|
687
|
+
if (primitiveTypes.size > 1) {
|
|
688
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. Enums must not mix primitive types.`);
|
|
689
|
+
}
|
|
690
|
+
if (schemaType !== undefined) {
|
|
691
|
+
const matchesSchemaType = filteredValues.every((value) => {
|
|
692
|
+
if (schemaType === "integer") {
|
|
693
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
694
|
+
}
|
|
695
|
+
if (schemaType === "number") {
|
|
696
|
+
return typeof value === "number";
|
|
697
|
+
}
|
|
698
|
+
if (schemaType === "string" || schemaType === "boolean") {
|
|
699
|
+
return typeof value === schemaType;
|
|
700
|
+
}
|
|
701
|
+
return true;
|
|
702
|
+
});
|
|
703
|
+
if (!matchesSchemaType) {
|
|
704
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. Enum values must match declared schema.type ${JSON.stringify(schemaType)}.`);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return filteredValues.filter(isEnumPrimitiveValue);
|
|
708
|
+
}
|
|
709
|
+
function resolveLocalReference(document, ref, operationId, context) {
|
|
710
|
+
if (!ref.startsWith("#/")) {
|
|
711
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported external $ref ${JSON.stringify(ref)} in ${context}.`);
|
|
712
|
+
}
|
|
713
|
+
let current = document;
|
|
714
|
+
for (const segment of ref.slice(2).split("/").map(unescapeJsonPointerSegment)) {
|
|
715
|
+
if (typeof current !== "object" || current === null || !(segment in current)) {
|
|
716
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} references missing $ref ${JSON.stringify(ref)} in ${context}.`);
|
|
717
|
+
}
|
|
718
|
+
current = current[segment];
|
|
719
|
+
}
|
|
720
|
+
return current;
|
|
721
|
+
}
|
|
722
|
+
function unescapeJsonPointerSegment(segment) {
|
|
723
|
+
return segment.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
724
|
+
}
|
|
725
|
+
function expectParameter(document, parameter, operationId, refChain = []) {
|
|
726
|
+
if (isReferenceObject(parameter)) {
|
|
727
|
+
assertAcyclicRef(parameter.$ref, refChain, operationId, "parameter");
|
|
728
|
+
return expectParameter(document, resolveLocalReference(document, parameter.$ref, operationId, "parameter"), operationId, [...refChain, parameter.$ref]);
|
|
729
|
+
}
|
|
730
|
+
if (parameter.content !== undefined) {
|
|
731
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} parameter ${JSON.stringify(parameter.name)} uses unsupported parameter.content. Define path/query parameters with parameter.schema in v1.`);
|
|
732
|
+
}
|
|
733
|
+
if (parameter.in !== "path" && parameter.in !== "query") {
|
|
734
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported parameter location ${JSON.stringify(parameter.in)}. Only path and query parameters are supported in v1; use auth or handwritten commands for headers/cookies.`);
|
|
735
|
+
}
|
|
736
|
+
return parameter;
|
|
737
|
+
}
|
|
738
|
+
function expectOperation(document, operation, method, path, refChain = []) {
|
|
739
|
+
if (!isReferenceObject(operation)) {
|
|
740
|
+
return operation;
|
|
741
|
+
}
|
|
742
|
+
const operationId = `${method.toUpperCase()} ${path}`;
|
|
743
|
+
const context = `operation ${method.toUpperCase()} ${path}`;
|
|
744
|
+
assertAcyclicRef(operation.$ref, refChain, operationId, context);
|
|
745
|
+
return expectOperation(document, resolveLocalReference(document, operation.$ref, operationId, context), method, path, [...refChain, operation.$ref]);
|
|
746
|
+
}
|
|
747
|
+
function isEnumPrimitiveValue(value) {
|
|
748
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
749
|
+
}
|
|
750
|
+
function expectRequestBody(document, requestBody, operationId, context, refChain = []) {
|
|
751
|
+
if (!isReferenceObject(requestBody)) {
|
|
752
|
+
return requestBody;
|
|
753
|
+
}
|
|
754
|
+
assertAcyclicRef(requestBody.$ref, refChain, operationId, context);
|
|
755
|
+
return expectRequestBody(document, resolveLocalReference(document, requestBody.$ref, operationId, context), operationId, context, [...refChain, requestBody.$ref]);
|
|
756
|
+
}
|
|
757
|
+
function expectResponse(document, response, operationId, statusCode, refChain = []) {
|
|
758
|
+
if (!isReferenceObject(response)) {
|
|
759
|
+
return response;
|
|
760
|
+
}
|
|
761
|
+
const context = `success response for status ${JSON.stringify(statusCode)}`;
|
|
762
|
+
assertAcyclicRef(response.$ref, refChain, operationId, context);
|
|
763
|
+
return expectResponse(document, resolveLocalReference(document, response.$ref, operationId, context), operationId, statusCode, [...refChain, response.$ref]);
|
|
764
|
+
}
|
|
765
|
+
function expectSchema(document, schema, operationId, context, refChain = []) {
|
|
766
|
+
const resolvedSchema = resolveSchema(document, schema, operationId, context, refChain);
|
|
767
|
+
const compositionKeyword = getCompositionKeyword(resolvedSchema);
|
|
768
|
+
if (compositionKeyword !== undefined) {
|
|
769
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. JSON Schema composition keyword ${JSON.stringify(compositionKeyword)} is not supported in v1.`);
|
|
770
|
+
}
|
|
771
|
+
return resolvedSchema;
|
|
772
|
+
}
|
|
773
|
+
function resolveSchema(document, schema, operationId, context, refChain = []) {
|
|
774
|
+
if (schema === undefined) {
|
|
775
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} is missing a schema for ${context}.`);
|
|
776
|
+
}
|
|
777
|
+
if (isReferenceObject(schema)) {
|
|
778
|
+
assertAcyclicRef(schema.$ref, refChain, operationId, context);
|
|
779
|
+
return resolveSchema(document, resolveLocalReference(document, schema.$ref, operationId, context), operationId, context, [...refChain, schema.$ref]);
|
|
780
|
+
}
|
|
781
|
+
return schema;
|
|
782
|
+
}
|
|
783
|
+
function getCompositionKeyword(schema) {
|
|
784
|
+
for (const keyword of ["allOf", "anyOf", "oneOf"]) {
|
|
785
|
+
if (schema[keyword] !== undefined) {
|
|
786
|
+
return keyword;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return undefined;
|
|
790
|
+
}
|
|
791
|
+
function resolveNullableAnyOfSchema(document, schema, operationId, context) {
|
|
792
|
+
const variants = schema.anyOf;
|
|
793
|
+
if (variants === undefined || variants.length !== 2) {
|
|
794
|
+
return undefined;
|
|
795
|
+
}
|
|
796
|
+
const resolvedVariants = variants.map((variant, index) => resolveSchema(document, variant, operationId, `${context} anyOf variant ${index}`));
|
|
797
|
+
const nullVariantIndex = resolvedVariants.findIndex(isExplicitNullSchema);
|
|
798
|
+
if (nullVariantIndex === -1) {
|
|
799
|
+
return undefined;
|
|
800
|
+
}
|
|
801
|
+
const nonNullVariant = resolvedVariants[Number(!nullVariantIndex)];
|
|
802
|
+
if (nonNullVariant === undefined || getCompositionKeyword(nonNullVariant) !== undefined) {
|
|
803
|
+
return undefined;
|
|
804
|
+
}
|
|
805
|
+
return {
|
|
806
|
+
...nonNullVariant,
|
|
807
|
+
nullable: true
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
function isExplicitNullSchema(schema) {
|
|
811
|
+
return schema.type === "null";
|
|
812
|
+
}
|
|
813
|
+
function assertAcyclicRef(ref, refChain, operationId, context) {
|
|
814
|
+
if (!refChain.includes(ref)) {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses circular $ref chain in ${context}: ${[
|
|
818
|
+
...refChain,
|
|
819
|
+
ref
|
|
820
|
+
]
|
|
821
|
+
.map((value) => JSON.stringify(value))
|
|
822
|
+
.join(" -> ")}.`);
|
|
823
|
+
}
|
|
824
|
+
function expectArrayItemsSchema(document, schema, operationId, context) {
|
|
825
|
+
if (schema.items === undefined) {
|
|
826
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} is missing array items for ${context}.`);
|
|
827
|
+
}
|
|
828
|
+
return expectSchema(document, schema.items, operationId, `${context} items`);
|
|
829
|
+
}
|
|
830
|
+
function assertUniqueCommandPaths(commands) {
|
|
831
|
+
const seen = new Map();
|
|
832
|
+
for (const command of commands) {
|
|
833
|
+
const key = `${command.noun} ${command.verb}`;
|
|
834
|
+
const existing = seen.get(key);
|
|
835
|
+
if (existing !== undefined) {
|
|
836
|
+
throw new UserError(`Generated command path ${JSON.stringify(key)} is defined more than once (${JSON.stringify(existing.operationId)} and ${JSON.stringify(command.operationId)}).`);
|
|
837
|
+
}
|
|
838
|
+
seen.set(key, command);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
function createCommandFile(options) {
|
|
842
|
+
const requiresUserError = options.preflightBlocks.length > 0;
|
|
843
|
+
const lines = createGeneratedTypeScriptFileLines([
|
|
844
|
+
`spec-sha: ${options.specSha}`,
|
|
845
|
+
`operation-id: ${options.operationId}`
|
|
846
|
+
]);
|
|
847
|
+
lines.push(requiresUserError
|
|
848
|
+
? 'import { S, UserError } from "agent-kit";'
|
|
849
|
+
: 'import { S } from "agent-kit";', 'import { requestJson, defineApiCommand } from "agent-kit-openapi";', "", `export const ${options.exportName} = defineApiCommand({`, ` name: ${JSON.stringify(options.verb)},`);
|
|
850
|
+
if (options.description !== undefined) {
|
|
851
|
+
lines.push(` description: ${JSON.stringify(options.description)},`);
|
|
852
|
+
}
|
|
853
|
+
lines.push(' scope: ["cli", "mcp", "sdk"] as const,');
|
|
854
|
+
if (options.confirm) {
|
|
855
|
+
lines.push(" confirm: true,");
|
|
856
|
+
}
|
|
857
|
+
lines.push(" params: S.Object({");
|
|
858
|
+
lines.push(...renderParamLines(options.params));
|
|
859
|
+
lines.push(options.paramsSchemaOptions?.additionalProperties === false
|
|
860
|
+
? " }, { additionalProperties: false }),"
|
|
861
|
+
: " }),");
|
|
862
|
+
lines.push(" handler: async ({ params, baseUrl, tokenSource, fetch }) => {");
|
|
863
|
+
lines.push(...options.preflightBlocks.flatMap((block) => renderPreflightBlock(block)));
|
|
864
|
+
lines.push(" return requestJson({");
|
|
865
|
+
lines.push(" baseUrl,");
|
|
866
|
+
lines.push(` path: ${JSON.stringify(options.path)},`);
|
|
867
|
+
lines.push(` method: ${JSON.stringify(options.method)},`);
|
|
868
|
+
lines.push(` auth: ${JSON.stringify(options.auth)},`);
|
|
869
|
+
lines.push(" tokenSource,");
|
|
870
|
+
lines.push(" fetch,");
|
|
871
|
+
lines.push(" dryRun: params.dryRun,");
|
|
872
|
+
lines.push(" verbose: params.verbose,");
|
|
873
|
+
lines.push(...renderRequestShape(options.requestFields, options.sectionRenders, options.optionalSections));
|
|
874
|
+
lines.push(" });");
|
|
875
|
+
lines.push(" },");
|
|
876
|
+
lines.push("});");
|
|
877
|
+
lines.push("");
|
|
878
|
+
return lines.join("\n");
|
|
879
|
+
}
|
|
880
|
+
function mergeCommandDescriptions(operationDescription, requestBodyDescription) {
|
|
881
|
+
const operationText = operationDescription === undefined || operationDescription.length === 0
|
|
882
|
+
? undefined
|
|
883
|
+
: operationDescription;
|
|
884
|
+
const requestBodyText = requestBodyDescription === undefined || requestBodyDescription.length === 0
|
|
885
|
+
? undefined
|
|
886
|
+
: requestBodyDescription;
|
|
887
|
+
if (operationText === undefined ||
|
|
888
|
+
requestBodyText === undefined ||
|
|
889
|
+
operationText === requestBodyText) {
|
|
890
|
+
return operationText ?? requestBodyText;
|
|
891
|
+
}
|
|
892
|
+
return `${operationText}\n\nRequest body: ${requestBodyText}`;
|
|
893
|
+
}
|
|
894
|
+
function getOperationAuthMode(document, operation, operationId) {
|
|
895
|
+
const security = operation.security ?? document.security;
|
|
896
|
+
const securityScope = operation.security === undefined ? "document" : "operation";
|
|
897
|
+
const definedSchemes = document.components?.securitySchemes;
|
|
898
|
+
for (const requirement of security ?? []) {
|
|
899
|
+
for (const schemeName of Object.keys(requirement)) {
|
|
900
|
+
if (definedSchemes !== undefined && schemeName in definedSchemes) {
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} references undefined security scheme ${JSON.stringify(schemeName)} in ${securityScope} security. Expected components.securitySchemes to define it.`);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
return security === undefined || security.length === 0 ? "none" : "required";
|
|
907
|
+
}
|
|
908
|
+
function assertSupportedHttpMethods(path, pathItem) {
|
|
909
|
+
const rawPathItem = pathItem;
|
|
910
|
+
for (const method of UNSUPPORTED_HTTP_METHODS) {
|
|
911
|
+
const operation = rawPathItem[method];
|
|
912
|
+
if (operation === undefined) {
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
throw new UserError(`Operation ${JSON.stringify(getOperationId(path, method, operation))} uses unsupported HTTP method ${JSON.stringify(method.toUpperCase())}. Supported in v1: GET, POST, PUT, PATCH, DELETE.`);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
function getOperationId(path, method, operation) {
|
|
919
|
+
if (operation !== null &&
|
|
920
|
+
typeof operation === "object" &&
|
|
921
|
+
"operationId" in operation &&
|
|
922
|
+
typeof operation.operationId === "string" &&
|
|
923
|
+
operation.operationId.length > 0) {
|
|
924
|
+
return operation.operationId;
|
|
925
|
+
}
|
|
926
|
+
return `${method.toUpperCase()} ${path}`;
|
|
927
|
+
}
|
|
928
|
+
function stripNullable(schema) {
|
|
929
|
+
if (schema.nullable !== true) {
|
|
930
|
+
return schema;
|
|
931
|
+
}
|
|
932
|
+
return { ...schema, nullable: undefined };
|
|
933
|
+
}
|
|
934
|
+
function renderParamLines(params) {
|
|
935
|
+
return params.map((param) => ` ${renderObjectKey(param.paramName)}: ${renderParamSchema(param)},`);
|
|
936
|
+
}
|
|
937
|
+
function renderParamSchema(param) {
|
|
938
|
+
const schema = renderDefinition(param.definition, param.description, param.shortFlag, param.scope);
|
|
939
|
+
return param.optional ? `S.Optional(${schema})` : schema;
|
|
940
|
+
}
|
|
941
|
+
function renderDefinition(definition, description, shortFlag, scope) {
|
|
942
|
+
const options = renderSchemaOptions({
|
|
943
|
+
definition,
|
|
944
|
+
description,
|
|
945
|
+
shortFlag,
|
|
946
|
+
scope
|
|
947
|
+
});
|
|
948
|
+
const renderer = DEFINITION_RENDERERS[definition.kind];
|
|
949
|
+
return renderer(definition, options);
|
|
950
|
+
}
|
|
951
|
+
const DEFINITION_RENDERERS = {
|
|
952
|
+
array: (definition, options) => renderSchemaCall("S.Array", renderDefinition(definition.itemDefinition, undefined, undefined, undefined), options),
|
|
953
|
+
boolean: (_definition, options) => renderSchemaCall("S.Boolean", options),
|
|
954
|
+
enum: (definition, options) => renderSchemaCall("S.Enum", renderConstArray(definition.enumValues), options),
|
|
955
|
+
number: (_definition, options) => renderSchemaCall("S.Number", options),
|
|
956
|
+
string: (_definition, options) => renderSchemaCall("S.String", options)
|
|
957
|
+
};
|
|
958
|
+
function renderSchemaCall(builder, ...args) {
|
|
959
|
+
return `${builder}(${args.filter((arg) => arg !== undefined).join(", ")})`;
|
|
960
|
+
}
|
|
961
|
+
function renderSchemaOptions(param) {
|
|
962
|
+
const entries = collectSchemaOptionEntries(param).map(({ key, value }) => `${key}: ${renderSchemaOptionValue(value)}`);
|
|
963
|
+
return entries.length === 0 ? undefined : `{ ${entries.join(", ")} }`;
|
|
964
|
+
}
|
|
965
|
+
function renderConstArray(values) {
|
|
966
|
+
return `${JSON.stringify(values)} as const`;
|
|
967
|
+
}
|
|
968
|
+
function renderObjectKey(name) {
|
|
969
|
+
if (name === normalizeParamName(name) && isIdentifierName(name)) {
|
|
970
|
+
return name;
|
|
971
|
+
}
|
|
972
|
+
return JSON.stringify(name);
|
|
973
|
+
}
|
|
974
|
+
function assertValidGeneratedNoun(operationId, noun) {
|
|
975
|
+
const identifier = toCamelCase(noun);
|
|
976
|
+
if (isTypeScriptIdentifier(identifier)) {
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} derives command noun ${JSON.stringify(noun)}, which maps to invalid TypeScript identifier ${JSON.stringify(identifier)}.`);
|
|
980
|
+
}
|
|
981
|
+
export function collectSchemaOptionEntries(param) {
|
|
982
|
+
return SCHEMA_OPTION_SOURCES.flatMap(({ key, get }) => {
|
|
983
|
+
const value = get(param);
|
|
984
|
+
return value === undefined ? [] : [{ key, value }];
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
function renderSchemaOptionValue(value) {
|
|
988
|
+
if (Array.isArray(value)) {
|
|
989
|
+
return `[${value.map((entry) => renderSchemaOptionValue(entry)).join(", ")}]`;
|
|
990
|
+
}
|
|
991
|
+
return JSON.stringify(value);
|
|
992
|
+
}
|
|
993
|
+
const RESERVED_TYPESCRIPT_IDENTIFIERS = new Set([
|
|
994
|
+
"abstract",
|
|
995
|
+
"any",
|
|
996
|
+
"as",
|
|
997
|
+
"asserts",
|
|
998
|
+
"async",
|
|
999
|
+
"await",
|
|
1000
|
+
"bigint",
|
|
1001
|
+
"boolean",
|
|
1002
|
+
"break",
|
|
1003
|
+
"case",
|
|
1004
|
+
"catch",
|
|
1005
|
+
"class",
|
|
1006
|
+
"const",
|
|
1007
|
+
"continue",
|
|
1008
|
+
"debugger",
|
|
1009
|
+
"declare",
|
|
1010
|
+
"default",
|
|
1011
|
+
"delete",
|
|
1012
|
+
"do",
|
|
1013
|
+
"else",
|
|
1014
|
+
"enum",
|
|
1015
|
+
"export",
|
|
1016
|
+
"extends",
|
|
1017
|
+
"false",
|
|
1018
|
+
"finally",
|
|
1019
|
+
"for",
|
|
1020
|
+
"function",
|
|
1021
|
+
"get",
|
|
1022
|
+
"if",
|
|
1023
|
+
"implements",
|
|
1024
|
+
"import",
|
|
1025
|
+
"in",
|
|
1026
|
+
"infer",
|
|
1027
|
+
"instanceof",
|
|
1028
|
+
"interface",
|
|
1029
|
+
"is",
|
|
1030
|
+
"keyof",
|
|
1031
|
+
"let",
|
|
1032
|
+
"module",
|
|
1033
|
+
"namespace",
|
|
1034
|
+
"never",
|
|
1035
|
+
"new",
|
|
1036
|
+
"null",
|
|
1037
|
+
"number",
|
|
1038
|
+
"object",
|
|
1039
|
+
"package",
|
|
1040
|
+
"private",
|
|
1041
|
+
"protected",
|
|
1042
|
+
"public",
|
|
1043
|
+
"readonly",
|
|
1044
|
+
"return",
|
|
1045
|
+
"satisfies",
|
|
1046
|
+
"set",
|
|
1047
|
+
"static",
|
|
1048
|
+
"string",
|
|
1049
|
+
"super",
|
|
1050
|
+
"switch",
|
|
1051
|
+
"symbol",
|
|
1052
|
+
"this",
|
|
1053
|
+
"throw",
|
|
1054
|
+
"true",
|
|
1055
|
+
"try",
|
|
1056
|
+
"type",
|
|
1057
|
+
"typeof",
|
|
1058
|
+
"undefined",
|
|
1059
|
+
"unique",
|
|
1060
|
+
"unknown",
|
|
1061
|
+
"var",
|
|
1062
|
+
"void",
|
|
1063
|
+
"while",
|
|
1064
|
+
"with",
|
|
1065
|
+
"yield"
|
|
1066
|
+
]);
|
|
1067
|
+
function isTypeScriptIdentifier(value) {
|
|
1068
|
+
return isIdentifierName(value) && !RESERVED_TYPESCRIPT_IDENTIFIERS.has(value);
|
|
1069
|
+
}
|
|
1070
|
+
function resolveQueryArraySerialization(parameter, operationId) {
|
|
1071
|
+
const style = parameter.style ?? "form";
|
|
1072
|
+
const explode = parameter.explode ?? style === "form";
|
|
1073
|
+
if (style === "form") {
|
|
1074
|
+
return explode ? "repeat" : "comma";
|
|
1075
|
+
}
|
|
1076
|
+
if (style === "pipeDelimited" && explode === false) {
|
|
1077
|
+
return "pipe";
|
|
1078
|
+
}
|
|
1079
|
+
throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported query-array serialization for parameter ${JSON.stringify(parameter.name)}. Supported in v1: form (explode true/false) and pipeDelimited.`);
|
|
1080
|
+
}
|
|
1081
|
+
function createIndexFile(commands) {
|
|
1082
|
+
const groups = groupByNoun(commands);
|
|
1083
|
+
if (groups.length === 0) {
|
|
1084
|
+
return {
|
|
1085
|
+
path: "index.ts",
|
|
1086
|
+
contents: createGeneratedTypeScriptFile(["export {};", ""])
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
const lines = createGeneratedTypeScriptFileLines();
|
|
1090
|
+
lines.push('import { defineGroup } from "agent-kit";');
|
|
1091
|
+
for (const { commands: nounCommands } of groups) {
|
|
1092
|
+
for (const command of nounCommands) {
|
|
1093
|
+
lines.push(`import { ${command.exportName} } from ${JSON.stringify(`./${command.filePath.replace(/\.ts$/, ".js")}`)};`);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
if (lines.length > 1) {
|
|
1097
|
+
lines.push("");
|
|
1098
|
+
}
|
|
1099
|
+
for (const { noun, commands: nounCommands } of groups) {
|
|
1100
|
+
lines.push(`export const ${toCamelCase(noun)} = defineGroup({`);
|
|
1101
|
+
lines.push(` name: ${JSON.stringify(noun)},`);
|
|
1102
|
+
lines.push(` children: [${nounCommands.map((command) => command.exportName).join(", ")}],`);
|
|
1103
|
+
lines.push("});");
|
|
1104
|
+
lines.push("");
|
|
1105
|
+
}
|
|
1106
|
+
return {
|
|
1107
|
+
path: "index.ts",
|
|
1108
|
+
contents: lines.join("\n")
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
function createGeneratedTypeScriptFile(bodyLines, metadataLines = []) {
|
|
1112
|
+
return [...createGeneratedTypeScriptFileLines(metadataLines), ...bodyLines].join("\n");
|
|
1113
|
+
}
|
|
1114
|
+
function createGeneratedTypeScriptFileLines(metadataLines = []) {
|
|
1115
|
+
return [
|
|
1116
|
+
"/**",
|
|
1117
|
+
" * Generated by agent-kit-openapi.",
|
|
1118
|
+
...metadataLines.map((line) => ` * ${line}`),
|
|
1119
|
+
" */"
|
|
1120
|
+
];
|
|
1121
|
+
}
|
|
1122
|
+
function compareGeneratedCommandPaths(left, right) {
|
|
1123
|
+
const nounCompare = left.noun.localeCompare(right.noun);
|
|
1124
|
+
if (nounCompare !== 0) {
|
|
1125
|
+
return nounCompare;
|
|
1126
|
+
}
|
|
1127
|
+
return left.verb.localeCompare(right.verb);
|
|
1128
|
+
}
|
|
1129
|
+
function isReferenceObject(value) {
|
|
1130
|
+
return typeof value === "object" && value !== null && "$ref" in value;
|
|
1131
|
+
}
|