@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,15 @@
1
+ import { type AstNode, type Binding } from "./model.js";
2
+ export declare class ScopeStack {
3
+ private readonly scopes;
4
+ constructor(scopes: Array<Map<string, Binding>>);
5
+ reserve(name: string, mutable: boolean, node: AstNode): void;
6
+ initialize(name: string, value: unknown, node: AstNode): void;
7
+ declare(name: string, value: unknown, mutable: boolean, node: AstNode): void;
8
+ get(name: string, node: AstNode): unknown;
9
+ set(name: string, value: unknown, node: AstNode): unknown;
10
+ resolve(name: string): Binding | undefined;
11
+ current(): Map<string, Binding>;
12
+ push(scope?: Map<string, Binding>): void;
13
+ pop(): void;
14
+ capture(): Array<Map<string, Binding>>;
15
+ }
@@ -0,0 +1,79 @@
1
+ import { InterpreterRuntimeError } from "./model.js";
2
+ export class ScopeStack {
3
+ scopes;
4
+ constructor(scopes) {
5
+ this.scopes = scopes;
6
+ }
7
+ reserve(name, mutable, node) {
8
+ const scope = this.current();
9
+ if (scope.has(name)) {
10
+ throw new InterpreterRuntimeError(`Identifier '${name}' has already been declared.`, node);
11
+ }
12
+ scope.set(name, { mutable, value: undefined, initialized: false });
13
+ }
14
+ initialize(name, value, node) {
15
+ const binding = this.current().get(name);
16
+ if (!binding || binding.initialized !== false) {
17
+ throw new InterpreterRuntimeError(`Identifier '${name}' has not been reserved for initialization.`, node);
18
+ }
19
+ binding.value = value;
20
+ binding.initialized = true;
21
+ }
22
+ declare(name, value, mutable, node) {
23
+ const scope = this.current();
24
+ if (scope.has(name)) {
25
+ throw new InterpreterRuntimeError(`Identifier '${name}' has already been declared.`, node);
26
+ }
27
+ scope.set(name, { mutable, value, initialized: true });
28
+ }
29
+ get(name, node) {
30
+ const binding = this.resolve(name);
31
+ if (!binding) {
32
+ throw new InterpreterRuntimeError(`Unknown identifier '${name}'.`, node).as("ReferenceError");
33
+ }
34
+ if (binding.initialized === false) {
35
+ throw new InterpreterRuntimeError(`Cannot access '${name}' before initialization.`, node).as("ReferenceError");
36
+ }
37
+ return binding.value;
38
+ }
39
+ set(name, value, node) {
40
+ const binding = this.resolve(name);
41
+ if (!binding) {
42
+ throw new InterpreterRuntimeError(`Unknown identifier '${name}'.`, node).as("ReferenceError");
43
+ }
44
+ if (binding.initialized === false) {
45
+ throw new InterpreterRuntimeError(`Cannot access '${name}' before initialization.`, node).as("ReferenceError");
46
+ }
47
+ if (!binding.mutable) {
48
+ throw new InterpreterRuntimeError(`Cannot assign to constant '${name}'.`, node).as("TypeError");
49
+ }
50
+ binding.value = value;
51
+ return value;
52
+ }
53
+ resolve(name) {
54
+ for (let index = this.scopes.length - 1; index >= 0; index -= 1) {
55
+ const scope = this.scopes[index];
56
+ const binding = scope?.get(name);
57
+ if (binding) {
58
+ return binding;
59
+ }
60
+ }
61
+ return undefined;
62
+ }
63
+ current() {
64
+ const scope = this.scopes[this.scopes.length - 1];
65
+ if (!scope) {
66
+ throw new InterpreterRuntimeError("Interpreter scope stack is empty.");
67
+ }
68
+ return scope;
69
+ }
70
+ push(scope = new Map()) {
71
+ this.scopes.push(scope);
72
+ }
73
+ pop() {
74
+ this.scopes.pop();
75
+ }
76
+ capture() {
77
+ return this.scopes.slice();
78
+ }
79
+ }
@@ -0,0 +1,5 @@
1
+ export interface TranspileResult {
2
+ readonly outputText: string;
3
+ readonly error?: string;
4
+ }
5
+ export declare const transpile: (source: string) => TranspileResult;
@@ -0,0 +1,19 @@
1
+ import { DiagnosticCategory, ModuleKind, ScriptTarget, flattenDiagnosticMessageText, transpileModule } from "typescript";
2
+ // Full TypeScript transpilation on node/bun runtimes.
3
+ export const transpile = (source) => {
4
+ const transpiled = transpileModule(source, {
5
+ reportDiagnostics: true,
6
+ compilerOptions: {
7
+ target: ScriptTarget.ESNext,
8
+ module: ModuleKind.ESNext,
9
+ },
10
+ });
11
+ const diagnostic = transpiled.diagnostics?.find((item) => item.category === DiagnosticCategory.Error);
12
+ if (diagnostic) {
13
+ return {
14
+ outputText: transpiled.outputText,
15
+ error: flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
16
+ };
17
+ }
18
+ return { outputText: transpiled.outputText };
19
+ };
@@ -0,0 +1,5 @@
1
+ export interface TranspileResult {
2
+ readonly outputText: string;
3
+ readonly error?: string;
4
+ }
5
+ export declare const transpile: (source: string) => TranspileResult;
@@ -0,0 +1,6 @@
1
+ // workerd profile: the typescript compiler is ~11 MiB and probes node
2
+ // internals at module init, so codemode programs are passed through
3
+ // untranspiled. Plain-JS programs (the overwhelmingly common case) parse
4
+ // fine downstream via acorn; TypeScript-only syntax surfaces as a parse
5
+ // error from the interpreter instead of a transpile diagnostic.
6
+ export const transpile = (source) => ({ outputText: source });
@@ -0,0 +1,7 @@
1
+ import type { Options, Result } from "./types.js";
2
+ export type { AuthResolver, Credential, Document, Operation, Options, Result, SecurityScheme, Skipped, Tools, } from "./types.js";
3
+ /**
4
+ * Builds one CodeMode tool per representable OpenAPI 3.x operation. Auth remains host-side,
5
+ * tools require `HttpClient.HttpClient`, and unrepresentable operations land in `skipped`.
6
+ */
7
+ export declare const fromSpec: (options: Options) => Result;
@@ -0,0 +1,101 @@
1
+ import { HttpClient } from "effect/unstable/http";
2
+ import { make } from "../tool.js";
3
+ import { invoke } from "./runtime.js";
4
+ import { componentDefinitions, hasDirectionalSchemas, inputSchema, isRecord, methods, nonEmptyString, operationInput, operationOutput, operationPath, operationSecurityRequirements, securityRequirements, securitySchemes, specServerUrl, validateBaseUrl, } from "./spec.js";
5
+ /**
6
+ * Builds one CodeMode tool per representable OpenAPI 3.x operation. Auth remains host-side,
7
+ * tools require `HttpClient.HttpClient`, and unrepresentable operations land in `skipped`.
8
+ */
9
+ export const fromSpec = (options) => {
10
+ const document = options.spec;
11
+ const schemes = securitySchemes(document);
12
+ const defaultSecurity = securityRequirements(document.security);
13
+ const requestDefinitions = componentDefinitions(document, "request");
14
+ const responseDefinitions = hasDirectionalSchemas(document)
15
+ ? componentDefinitions(document, "response")
16
+ : requestDefinitions;
17
+ const paths = isRecord(document.paths) ? document.paths : {};
18
+ const used = new Set();
19
+ const namespaces = new Set();
20
+ const skipped = [];
21
+ const tools = Object.create(null);
22
+ for (const [path, pathValue] of Object.entries(paths)) {
23
+ if (!isRecord(pathValue))
24
+ continue;
25
+ for (const [method, operationValue] of Object.entries(pathValue)) {
26
+ if (!methods.has(method) || !isRecord(operationValue))
27
+ continue;
28
+ const segments = operationPath(method, path, operationValue, used, namespaces);
29
+ const operation = {
30
+ operationId: nonEmptyString(operationValue.operationId),
31
+ method: method.toUpperCase(),
32
+ path,
33
+ summary: nonEmptyString(operationValue.summary),
34
+ description: nonEmptyString(operationValue.description),
35
+ };
36
+ const output = operationOutput(document, operationValue, responseDefinitions);
37
+ if (!output.ok) {
38
+ skipped.push({ method: operation.method, path, reason: output.reason });
39
+ continue;
40
+ }
41
+ const resolvedBaseUrl = (() => {
42
+ if (options.baseUrl !== undefined)
43
+ return validateBaseUrl(options.baseUrl);
44
+ if (operationValue.servers !== undefined)
45
+ return specServerUrl(operationValue);
46
+ if (pathValue.servers !== undefined)
47
+ return specServerUrl(pathValue);
48
+ return specServerUrl(document);
49
+ })();
50
+ if (!resolvedBaseUrl.ok) {
51
+ skipped.push({ method: operation.method, path, reason: resolvedBaseUrl.reason });
52
+ continue;
53
+ }
54
+ const parsedInput = operationInput(document, pathValue, operationValue);
55
+ if (!parsedInput.ok) {
56
+ skipped.push({ method: operation.method, path, reason: parsedInput.reason });
57
+ continue;
58
+ }
59
+ const input = parsedInput.value;
60
+ const security = operationSecurityRequirements(operationValue.security, defaultSecurity, schemes);
61
+ if (!security.ok) {
62
+ skipped.push({ method: operation.method, path, reason: security.reason });
63
+ continue;
64
+ }
65
+ const plan = {
66
+ operation,
67
+ url: `${resolvedBaseUrl.value.replace(/\/+$/, "")}${path}`,
68
+ fields: input.fields,
69
+ body: input.body,
70
+ security: security.value,
71
+ schemes,
72
+ auth: options.auth,
73
+ headers: options.headers ?? {},
74
+ };
75
+ used.add(segments.join("."));
76
+ for (const index of segments.slice(0, -1).keys())
77
+ namespaces.add(segments.slice(0, index + 1).join("."));
78
+ setTool(tools, segments, make({
79
+ description: operation.description ?? operation.summary ?? `${operation.method} ${path}`,
80
+ input: inputSchema(input.fields, requestDefinitions),
81
+ output: output.value,
82
+ execute: (input) => invoke(plan, input),
83
+ }));
84
+ }
85
+ }
86
+ return { tools, skipped };
87
+ };
88
+ const setTool = (tools, path, tool) => {
89
+ const [head, ...rest] = path;
90
+ if (head === undefined)
91
+ return;
92
+ if (rest.length === 0) {
93
+ tools[head] = tool;
94
+ return;
95
+ }
96
+ const child = tools[head];
97
+ if (child === undefined || !isRecord(child) || child._tag === "CodeModeTool") {
98
+ tools[head] = Object.create(null);
99
+ }
100
+ setTool(tools[head], rest, tool);
101
+ };
@@ -0,0 +1,4 @@
1
+ import { Effect } from "effect";
2
+ import { HttpClient } from "effect/unstable/http";
3
+ import type { Plan } from "./types.js";
4
+ export declare const invoke: (plan: Plan, input: unknown) => Effect.Effect<unknown, unknown, HttpClient.HttpClient>;
@@ -0,0 +1,283 @@
1
+ import { Effect, Option, Schema, Stream } from "effect";
2
+ import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
3
+ import { ToolError, toolError } from "../tool-error.js";
4
+ import { isRecord, own } from "./spec.js";
5
+ const decodeJson = Schema.decodeUnknownOption(Schema.UnknownFromJsonString);
6
+ const maxErrorBodyChars = 1_024;
7
+ const maxResponseBodyBytes = 50 * 1024 * 1024;
8
+ export const invoke = (plan, input) => Effect.gen(function* () {
9
+ const value = isRecord(input) ? input : {};
10
+ let request = yield* buildRequest(plan, value);
11
+ const auth = yield* resolveAuth(plan);
12
+ for (const [name, item] of Object.entries(auth.query)) {
13
+ request = HttpClientRequest.setUrlParam(request, name, item);
14
+ }
15
+ request = HttpClientRequest.setHeaders(request, auth.headers);
16
+ const client = yield* HttpClient.HttpClient;
17
+ const response = yield* client
18
+ .execute(request)
19
+ .pipe(Effect.catch((cause) => Effect.fail(toolError(`${plan.operation.method} ${plan.operation.path} failed: transport error`, cause))));
20
+ const text = yield* readResponseBody(response, plan);
21
+ const mediaType = response.headers["content-type"]?.split(";")[0]?.trim().toLowerCase();
22
+ const json = mediaType === "application/json" || mediaType?.endsWith("+json") === true;
23
+ const decoded = text === "" ? Option.some(null) : json ? decodeJson(text) : Option.none();
24
+ const parsed = json ? Option.getOrElse(decoded, () => text) : text === "" ? null : text;
25
+ if (response.status < 200 || response.status >= 300) {
26
+ const rendered = typeof parsed === "string" ? parsed : (JSON.stringify(parsed) ?? "");
27
+ const summary = rendered === "" || rendered === "null"
28
+ ? "no response body"
29
+ : rendered.length > maxErrorBodyChars
30
+ ? `${rendered.slice(0, maxErrorBodyChars)}...`
31
+ : rendered;
32
+ return yield* Effect.fail(toolError(`${plan.operation.method} ${plan.operation.path} failed with HTTP ${response.status}: ${summary}`));
33
+ }
34
+ if (json && Option.isNone(decoded)) {
35
+ return yield* Effect.fail(toolError(`${plan.operation.method} ${plan.operation.path} returned malformed JSON.`));
36
+ }
37
+ return parsed;
38
+ });
39
+ const buildRequest = (plan, input) => Effect.gen(function* () {
40
+ // Validate model input before auth resolution can refresh credentials.
41
+ const url = buildUrl(plan, input);
42
+ if (url instanceof ToolError)
43
+ return yield* Effect.fail(url);
44
+ const missing = plan.fields.find((field) => field.required && field.location !== "path" && own(input, field.inputName) === undefined);
45
+ if (missing !== undefined) {
46
+ const label = missing.location === "body" ? "body field" : `${missing.location} parameter`;
47
+ return yield* Effect.fail(toolError(`Missing required ${label} '${missing.inputName}'.`));
48
+ }
49
+ let request = HttpClientRequest.make(plan.operation.method)(url);
50
+ const query = [];
51
+ for (const field of plan.fields) {
52
+ if (field.location !== "query")
53
+ continue;
54
+ const item = own(input, field.inputName);
55
+ if (item === undefined)
56
+ continue;
57
+ const serialized = serializeQuery(field, item);
58
+ if (serialized instanceof ToolError)
59
+ return yield* Effect.fail(serialized);
60
+ for (const parameter of serialized)
61
+ query.push(parameter);
62
+ }
63
+ if (query.length > 0)
64
+ request = HttpClientRequest.appendUrlParams(request, query);
65
+ request = HttpClientRequest.setHeaders(request, plan.headers);
66
+ for (const field of plan.fields) {
67
+ if (field.location !== "header")
68
+ continue;
69
+ const item = own(input, field.inputName);
70
+ if (item === undefined)
71
+ continue;
72
+ const serialized = serializeSimple(field, item, String);
73
+ if (serialized instanceof ToolError)
74
+ return yield* Effect.fail(serialized);
75
+ request = HttpClientRequest.setHeader(request, field.name, serialized);
76
+ }
77
+ const setBody = (value, mediaType) => HttpClientRequest.bodyJson(request, value).pipe(Effect.map((next) => HttpClientRequest.setHeader(next, "content-type", mediaType)), Effect.mapError((cause) => toolError(`Invalid JSON body for ${plan.operation.method} ${plan.operation.path}.`, cause)));
78
+ if (plan.body?.mode === "value") {
79
+ const field = plan.fields.find((field) => field.location === "body");
80
+ const body = field === undefined ? undefined : own(input, field.inputName);
81
+ if (body !== undefined)
82
+ request = yield* setBody(body, plan.body.mediaType);
83
+ }
84
+ if (plan.body?.mode === "object") {
85
+ const entries = plan.fields.flatMap((field) => {
86
+ if (field.location !== "body")
87
+ return [];
88
+ const item = own(input, field.inputName);
89
+ return item === undefined ? [] : [[field.name, item]];
90
+ });
91
+ if (plan.body.required || entries.length > 0) {
92
+ request = yield* setBody(Object.fromEntries(entries), plan.body.mediaType);
93
+ }
94
+ }
95
+ return request;
96
+ });
97
+ const resolveAuth = (plan) => Effect.gen(function* () {
98
+ const none = { headers: {}, query: {} };
99
+ if (plan.security.length === 0)
100
+ return none;
101
+ const unavailable = [];
102
+ alternatives: for (const requirement of plan.security) {
103
+ const names = Object.keys(requirement);
104
+ if (names.length === 0)
105
+ return none;
106
+ const credentials = [];
107
+ for (const name of names) {
108
+ const scheme = own(plan.schemes, name);
109
+ if (scheme === undefined || plan.auth === undefined) {
110
+ unavailable.push(name);
111
+ continue alternatives;
112
+ }
113
+ const credential = yield* plan.auth.resolve({
114
+ name,
115
+ definition: scheme,
116
+ scopes: requirement[name] ?? [],
117
+ operation: plan.operation,
118
+ });
119
+ if (credential === undefined) {
120
+ unavailable.push(name);
121
+ continue alternatives;
122
+ }
123
+ credentials.push([name, scheme, credential]);
124
+ }
125
+ const applied = applyCredentials(credentials);
126
+ return applied instanceof ToolError ? yield* Effect.fail(applied) : applied;
127
+ }
128
+ return yield* Effect.fail(toolError(`${plan.operation.method} ${plan.operation.path} requires authentication; no credential available for: ${[...new Set(unavailable)].join(", ")}.`));
129
+ });
130
+ const applyCredentials = (credentials) => {
131
+ const headers = new Map();
132
+ const query = new Map();
133
+ const add = (carrier, name, value) => {
134
+ const target = carrier === "header" ? headers : query;
135
+ if (target.has(name))
136
+ return toolError(`Authentication resolves multiple credentials for ${carrier} '${name}'.`);
137
+ target.set(name, value);
138
+ };
139
+ for (const [name, definition, credential] of credentials) {
140
+ if (credential.type === "bearer") {
141
+ const duplicate = add("header", "authorization", `Bearer ${credential.token}`);
142
+ if (duplicate !== undefined)
143
+ return duplicate;
144
+ continue;
145
+ }
146
+ if (credential.type === "basic") {
147
+ // Basic auth credentials are UTF-8; btoa rejects non-Latin-1 input.
148
+ const duplicate = add("header", "authorization", `Basic ${Buffer.from(`${credential.username}:${credential.password}`, "utf8").toString("base64")}`);
149
+ if (duplicate !== undefined)
150
+ return duplicate;
151
+ continue;
152
+ }
153
+ if (credential.type === "header") {
154
+ const duplicate = add("header", credential.name.toLowerCase(), credential.value);
155
+ if (duplicate !== undefined)
156
+ return duplicate;
157
+ continue;
158
+ }
159
+ if (definition.type !== "apiKey") {
160
+ return toolError(`Security scheme '${name}' is not an apiKey scheme; resolve a bearer, basic, or header credential for it.`);
161
+ }
162
+ if (definition.in === "cookie")
163
+ return toolError(`Cookie authentication '${name}' is not supported.`);
164
+ const parameter = definition.in === "header" ? definition.name.toLowerCase() : definition.name;
165
+ const duplicate = add(definition.in, parameter, credential.value);
166
+ if (duplicate !== undefined)
167
+ return duplicate;
168
+ }
169
+ return { headers: Object.fromEntries(headers), query: Object.fromEntries(query) };
170
+ };
171
+ const buildUrl = (plan, input) => {
172
+ let url = plan.url;
173
+ for (const field of plan.fields) {
174
+ if (field.location !== "path")
175
+ continue;
176
+ const item = own(input, field.inputName);
177
+ if (item === undefined) {
178
+ return toolError(`Missing required path parameter '${field.inputName}'.`);
179
+ }
180
+ const fieldValue = serializeSimple(field, item, (value) => encodeURIComponent(value).replace(/[!'()*]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`));
181
+ if (fieldValue instanceof ToolError)
182
+ return fieldValue;
183
+ // URL normalization collapses encoded `.` and `..`, which could retarget the request.
184
+ if (fieldValue === "" || fieldValue === "." || fieldValue === "..") {
185
+ return toolError(`Invalid path parameter '${field.inputName}'.`);
186
+ }
187
+ url = url.replaceAll(`{${field.name}}`, fieldValue);
188
+ }
189
+ const unresolved = url.match(/\{[^{}]+\}/);
190
+ if (unresolved !== null)
191
+ return toolError(`Unresolved path parameter ${unresolved[0]}.`);
192
+ return url;
193
+ };
194
+ const serializeSimple = (field, value, encode) => {
195
+ const scalar = (item) => item !== null && typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean"
196
+ ? toolError(`Parameter '${field.inputName}' contains an unsupported nested value.`)
197
+ : encode(String(item));
198
+ if (Array.isArray(value)) {
199
+ const items = value.map(scalar);
200
+ const invalid = items.find((item) => item instanceof ToolError);
201
+ return invalid ?? items.join(",");
202
+ }
203
+ if (!isRecord(value))
204
+ return scalar(value);
205
+ const entries = Object.entries(value).flatMap(([name, item]) => {
206
+ const rendered = scalar(item);
207
+ if (rendered instanceof ToolError)
208
+ return [rendered];
209
+ return field.explode ? [`${encode(name)}=${rendered}`] : [encode(name), rendered];
210
+ });
211
+ const invalid = entries.find((item) => item instanceof ToolError);
212
+ return invalid ?? entries.join(",");
213
+ };
214
+ const serializeQuery = (field, value) => {
215
+ if (field.style === "deepObject") {
216
+ if (!isRecord(value))
217
+ return toolError(`Deep-object parameter '${field.inputName}' must be an object.`);
218
+ const parameters = [];
219
+ for (const [name, item] of Object.entries(value)) {
220
+ if (item === undefined || (item !== null && typeof item === "object")) {
221
+ return toolError(`Deep-object parameter '${field.inputName}' contains an unsupported nested value.`);
222
+ }
223
+ parameters.push([`${field.name}[${name}]`, String(item)]);
224
+ }
225
+ return parameters;
226
+ }
227
+ if (Array.isArray(value)) {
228
+ if (!field.explode) {
229
+ const rendered = serializeSimple(field, value, String);
230
+ return rendered instanceof ToolError ? rendered : [[field.name, rendered]];
231
+ }
232
+ const parameters = [];
233
+ for (const item of value) {
234
+ if (item !== null && typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") {
235
+ return toolError(`Parameter '${field.inputName}' contains an unsupported nested value.`);
236
+ }
237
+ parameters.push([field.name, String(item)]);
238
+ }
239
+ return parameters;
240
+ }
241
+ if (isRecord(value) && field.explode) {
242
+ const parameters = [];
243
+ for (const [name, item] of Object.entries(value)) {
244
+ if (item === undefined || (item !== null && typeof item === "object")) {
245
+ return toolError(`Query parameter '${field.inputName}' contains an unsupported nested value.`);
246
+ }
247
+ parameters.push([name, String(item)]);
248
+ }
249
+ return parameters;
250
+ }
251
+ const rendered = serializeSimple(field, value, String);
252
+ return rendered instanceof ToolError ? rendered : [[field.name, rendered]];
253
+ };
254
+ const readResponseBody = (response, plan) => Effect.gen(function* () {
255
+ const contentLength = response.headers["content-length"];
256
+ const parsedSize = contentLength === undefined ? undefined : Number.parseInt(contentLength, 10);
257
+ const declaredSize = parsedSize !== undefined && Number.isSafeInteger(parsedSize) && parsedSize >= 0 ? parsedSize : undefined;
258
+ if (declaredSize !== undefined && declaredSize > maxResponseBodyBytes) {
259
+ return yield* Effect.fail(toolError(`${plan.operation.method} ${plan.operation.path} response exceeds 50 MiB.`));
260
+ }
261
+ let body = Buffer.allocUnsafe(Math.min(maxResponseBodyBytes, declaredSize ?? 64 * 1024));
262
+ let size = 0;
263
+ yield* Stream.runForEach(response.stream, (chunk) => {
264
+ if (size + chunk.byteLength > maxResponseBodyBytes) {
265
+ return Effect.fail(toolError(`${plan.operation.method} ${plan.operation.path} response exceeds 50 MiB.`));
266
+ }
267
+ if (size + chunk.byteLength > body.byteLength) {
268
+ const grown = Buffer.allocUnsafe(Math.min(maxResponseBodyBytes, Math.max(size + chunk.byteLength, body.byteLength * 2)));
269
+ body.copy(grown, 0, 0, size);
270
+ body = grown;
271
+ }
272
+ body.set(chunk, size);
273
+ size += chunk.byteLength;
274
+ return Effect.void;
275
+ }).pipe(Effect.catch((cause) => {
276
+ if (cause instanceof ToolError)
277
+ return Effect.fail(cause);
278
+ if (cause.reason._tag === "EmptyBodyError")
279
+ return Effect.void;
280
+ return Effect.fail(toolError(`${plan.operation.method} ${plan.operation.path} failed while reading the response body.`, cause));
281
+ }));
282
+ return new TextDecoder().decode(body.subarray(0, size));
283
+ });
@@ -0,0 +1,20 @@
1
+ import type { JsonSchema } from "../tool.js";
2
+ import type { Document, InputField, OperationInput, Parsed, SecurityRequirement, SecurityScheme } from "./types.js";
3
+ export declare const methods: Set<string>;
4
+ export declare const isRecord: (value: unknown) => value is Record<string, unknown>;
5
+ export declare const nonEmptyString: (value: unknown) => string | undefined;
6
+ export declare const own: <T>(record: Readonly<Record<string, T>>, key: string) => T | undefined;
7
+ export declare const resolve: (document: Document, value: unknown) => unknown;
8
+ type SchemaDirection = "request" | "response";
9
+ export declare const hasDirectionalSchemas: (document: Document) => boolean;
10
+ export declare const componentDefinitions: (document: Document, direction: SchemaDirection) => Readonly<Record<string, JsonSchema>>;
11
+ export declare const operationInput: (document: Document, pathItem: Record<string, unknown>, operation: Record<string, unknown>) => Parsed<OperationInput>;
12
+ export declare const inputSchema: (fields: ReadonlyArray<InputField>, definitions: Readonly<Record<string, JsonSchema>>) => JsonSchema;
13
+ export declare const operationOutput: (document: Document, operation: Record<string, unknown>, definitions: Readonly<Record<string, JsonSchema>>) => Parsed<JsonSchema | undefined>;
14
+ export declare const operationPath: (method: string, path: string, operation: Record<string, unknown>, used: ReadonlySet<string>, namespaces: ReadonlySet<string>) => ReadonlyArray<string>;
15
+ export declare const specServerUrl: (source: Record<string, unknown>) => Parsed<string>;
16
+ export declare const validateBaseUrl: (value: string) => Parsed<string>;
17
+ export declare const securityRequirements: (value: unknown) => Parsed<ReadonlyArray<SecurityRequirement>>;
18
+ export declare const operationSecurityRequirements: (value: unknown, defaults: Parsed<ReadonlyArray<SecurityRequirement>>, schemes: Readonly<Record<string, SecurityScheme>>) => Parsed<ReadonlyArray<SecurityRequirement>>;
19
+ export declare const securitySchemes: (document: Document) => Readonly<Record<string, SecurityScheme>>;
20
+ export {};