@robinbraemer/codemode 0.1.0 → 0.1.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/dist/index.js CHANGED
@@ -1,45 +1,158 @@
1
1
  import {
2
2
  IsolatedVMExecutor
3
- } from "./chunk-NSUQUO7S.js";
4
- import {
5
- QuickJSExecutor
6
- } from "./chunk-ZWSO33DZ.js";
3
+ } from "./chunk-M4AL5G4U.js";
7
4
  import "./chunk-PZ5AY32C.js";
8
5
 
9
6
  // src/executor/auto.ts
10
7
  async function createExecutor(options = {}) {
11
8
  try {
12
9
  await import("isolated-vm");
13
- const { IsolatedVMExecutor: IsolatedVMExecutor2 } = await import("./isolated-vm-57EIYEJM.js");
10
+ const { IsolatedVMExecutor: IsolatedVMExecutor2 } = await import("./isolated-vm-7REUQAB5.js");
14
11
  return new IsolatedVMExecutor2(options);
15
12
  } catch {
16
13
  }
17
- try {
18
- await import("quickjs-emscripten");
19
- const { QuickJSExecutor: QuickJSExecutor2 } = await import("./quickjs-BGVQS2YE.js");
20
- return new QuickJSExecutor2(options);
21
- } catch {
22
- }
23
14
  throw new Error(
24
- "No sandbox runtime found. Install one of:\n npm install isolated-vm # V8 isolates, fastest (Node.js only)\n npm install quickjs-emscripten # WASM sandbox, portable (Node.js, Bun, browsers)"
15
+ "No sandbox runtime found. Install isolated-vm:\n npm install isolated-vm # V8 isolates (Node.js)"
25
16
  );
26
17
  }
27
18
 
28
19
  // src/request-bridge.ts
29
- function createRequestBridge(handler, baseUrl) {
30
- return async (options) => {
31
- const { method, path, query, body, headers } = options;
20
+ var ALLOWED_METHODS = /* @__PURE__ */ new Set([
21
+ "GET",
22
+ "POST",
23
+ "PUT",
24
+ "PATCH",
25
+ "DELETE",
26
+ "HEAD",
27
+ "OPTIONS"
28
+ ]);
29
+ var BLOCKED_HEADER_PATTERNS = [
30
+ /^authorization$/i,
31
+ /^cookie$/i,
32
+ /^host$/i,
33
+ /^origin$/i,
34
+ /^referer$/i,
35
+ /^x-forwarded-/i,
36
+ /^x-real-ip$/i,
37
+ /^x-client-ip$/i,
38
+ /^cf-connecting-ip$/i,
39
+ /^true-client-ip$/i,
40
+ /^proxy-/i,
41
+ /^transfer-encoding$/i,
42
+ /^connection$/i,
43
+ /^upgrade$/i,
44
+ /^te$/i
45
+ ];
46
+ var DEFAULT_MAX_REQUESTS = 50;
47
+ var DEFAULT_MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
48
+ async function readResponseWithLimit(response, maxBytes) {
49
+ const reader = response.body?.getReader();
50
+ if (!reader) {
51
+ const text = await response.text();
52
+ if (text.length > maxBytes) {
53
+ throw new Error(
54
+ `Response too large: ${text.length} bytes exceeds limit of ${maxBytes} bytes`
55
+ );
56
+ }
57
+ return text;
58
+ }
59
+ const chunks = [];
60
+ let totalBytes = 0;
61
+ try {
62
+ for (; ; ) {
63
+ const { done, value } = await reader.read();
64
+ if (done) break;
65
+ totalBytes += value.byteLength;
66
+ if (totalBytes > maxBytes) {
67
+ throw new Error(
68
+ `Response too large: exceeded limit of ${maxBytes} bytes`
69
+ );
70
+ }
71
+ chunks.push(value);
72
+ }
73
+ } finally {
74
+ reader.releaseLock();
75
+ }
76
+ const decoder = new TextDecoder();
77
+ if (chunks.length === 1) return decoder.decode(chunks[0]);
78
+ const combined = new Uint8Array(totalBytes);
79
+ let offset = 0;
80
+ for (const chunk of chunks) {
81
+ combined.set(chunk, offset);
82
+ offset += chunk.byteLength;
83
+ }
84
+ return decoder.decode(combined);
85
+ }
86
+ function validatePath(path) {
87
+ if (path.includes("://")) {
88
+ throw new Error(`Invalid path: must not contain "://" \u2014 got "${path}"`);
89
+ }
90
+ if (!path.startsWith("/")) {
91
+ throw new Error(`Invalid path: must start with "/" \u2014 got "${path}"`);
92
+ }
93
+ if (path.startsWith("//")) {
94
+ throw new Error(`Invalid path: must not start with "//" \u2014 got "${path}"`);
95
+ }
96
+ if (path.includes("\0")) {
97
+ throw new Error("Invalid path: must not contain null bytes");
98
+ }
99
+ if (/[\r\n]/.test(path)) {
100
+ throw new Error("Invalid path: must not contain CR/LF characters");
101
+ }
102
+ if (path.includes("\\")) {
103
+ throw new Error("Invalid path: must not contain backslashes");
104
+ }
105
+ }
106
+ function filterHeaders(headers, allowedHeaders) {
107
+ if (!headers) return {};
108
+ if (allowedHeaders) {
109
+ const allowed = new Set(allowedHeaders.map((h) => h.toLowerCase()));
110
+ const filtered2 = {};
111
+ for (const [key, value] of Object.entries(headers)) {
112
+ if (allowed.has(key.toLowerCase())) {
113
+ filtered2[key] = value;
114
+ }
115
+ }
116
+ return filtered2;
117
+ }
118
+ const filtered = {};
119
+ for (const [key, value] of Object.entries(headers)) {
120
+ const blocked = BLOCKED_HEADER_PATTERNS.some((p) => p.test(key));
121
+ if (!blocked) {
122
+ filtered[key] = value;
123
+ }
124
+ }
125
+ return filtered;
126
+ }
127
+ function createRequestBridge(handler, baseUrl, options = {}) {
128
+ const maxRequests = options.maxRequests ?? DEFAULT_MAX_REQUESTS;
129
+ const maxResponseBytes = options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
130
+ const allowedHeaders = options.allowedHeaders;
131
+ let requestCount = 0;
132
+ return async (opts) => {
133
+ const { method, path, query, body, headers } = opts;
134
+ if (++requestCount > maxRequests) {
135
+ throw new Error(
136
+ `Request limit exceeded: max ${maxRequests} requests per execution`
137
+ );
138
+ }
139
+ const upperMethod = method.toUpperCase();
140
+ if (!ALLOWED_METHODS.has(upperMethod)) {
141
+ throw new Error(
142
+ `Invalid HTTP method: "${method}". Allowed: ${[...ALLOWED_METHODS].join(", ")}`
143
+ );
144
+ }
145
+ validatePath(path);
32
146
  const url = new URL(path, baseUrl);
33
147
  if (query) {
34
148
  for (const [key, value] of Object.entries(query)) {
35
149
  url.searchParams.set(key, String(value));
36
150
  }
37
151
  }
152
+ const filteredHeaders = filterHeaders(headers, allowedHeaders);
38
153
  const init = {
39
- method: method.toUpperCase(),
40
- headers: {
41
- ...headers
42
- }
154
+ method: upperMethod,
155
+ headers: { ...filteredHeaders }
43
156
  };
44
157
  if (body !== void 0 && body !== null) {
45
158
  init.body = JSON.stringify(body);
@@ -51,15 +164,16 @@ function createRequestBridge(handler, baseUrl) {
51
164
  responseHeaders[key] = value;
52
165
  });
53
166
  const contentType = response.headers.get("content-type") ?? "";
167
+ const text = await readResponseWithLimit(response, maxResponseBytes);
54
168
  let responseBody;
55
169
  if (contentType.includes("application/json")) {
56
170
  try {
57
- responseBody = await response.json();
171
+ responseBody = JSON.parse(text);
58
172
  } catch {
59
- responseBody = await response.text();
173
+ responseBody = text;
60
174
  }
61
175
  } else {
62
- responseBody = await response.text();
176
+ responseBody = text;
63
177
  }
64
178
  return {
65
179
  status: response.status,
@@ -69,46 +183,205 @@ function createRequestBridge(handler, baseUrl) {
69
183
  };
70
184
  }
71
185
 
186
+ // src/spec.ts
187
+ var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
188
+ var DEFAULT_MAX_REF_DEPTH = 50;
189
+ var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
190
+ function resolveRefs(obj, root, seen = /* @__PURE__ */ new Set(), maxDepth = DEFAULT_MAX_REF_DEPTH, _cache = /* @__PURE__ */ new Map()) {
191
+ if (obj === null || obj === void 0) return obj;
192
+ if (typeof obj !== "object") return obj;
193
+ if (Array.isArray(obj))
194
+ return obj.map((item) => resolveRefs(item, root, seen, maxDepth, _cache));
195
+ const record = obj;
196
+ if ("$ref" in record && typeof record.$ref === "string") {
197
+ const ref = record.$ref;
198
+ if (seen.has(ref)) return { $circular: ref };
199
+ if (seen.size >= maxDepth) {
200
+ return { $circular: ref, $reason: "max depth exceeded" };
201
+ }
202
+ if (_cache.has(ref)) return _cache.get(ref);
203
+ const parts = ref.replace("#/", "").split("/");
204
+ let resolved = root;
205
+ for (const part of parts) {
206
+ if (DANGEROUS_KEYS.has(part)) return { $ref: ref, $error: "unsafe ref path" };
207
+ resolved = resolved?.[part];
208
+ }
209
+ const branchSeen = new Set(seen);
210
+ branchSeen.add(ref);
211
+ const result2 = resolveRefs(resolved, root, branchSeen, maxDepth, _cache);
212
+ _cache.set(ref, result2);
213
+ return result2;
214
+ }
215
+ const result = {};
216
+ for (const [key, value] of Object.entries(record)) {
217
+ if (DANGEROUS_KEYS.has(key)) continue;
218
+ result[key] = resolveRefs(value, root, seen, maxDepth, _cache);
219
+ }
220
+ return result;
221
+ }
222
+ function extractServerBasePath(spec) {
223
+ const servers = spec.servers;
224
+ if (!servers?.length) return "";
225
+ const url = servers[0].url;
226
+ try {
227
+ const parsed = new URL(url);
228
+ return parsed.pathname.replace(/\/+$/, "");
229
+ } catch {
230
+ return url.replace(/\/+$/, "");
231
+ }
232
+ }
233
+ function processSpec(spec, maxRefDepth = DEFAULT_MAX_REF_DEPTH) {
234
+ const rawPaths = spec.paths ?? {};
235
+ const basePath = extractServerBasePath(spec);
236
+ const paths = {};
237
+ for (const [path, pathItem] of Object.entries(rawPaths)) {
238
+ if (!pathItem) continue;
239
+ const fullPath = basePath ? basePath + path : path;
240
+ paths[fullPath] = {};
241
+ for (const method of HTTP_METHODS) {
242
+ const op = pathItem[method];
243
+ if (op) {
244
+ paths[fullPath][method] = {
245
+ summary: op.summary,
246
+ description: op.description,
247
+ tags: op.tags,
248
+ operationId: op.operationId,
249
+ parameters: resolveRefs(
250
+ op.parameters,
251
+ spec,
252
+ void 0,
253
+ maxRefDepth
254
+ ),
255
+ requestBody: resolveRefs(
256
+ op.requestBody,
257
+ spec,
258
+ void 0,
259
+ maxRefDepth
260
+ ),
261
+ responses: resolveRefs(
262
+ op.responses,
263
+ spec,
264
+ void 0,
265
+ maxRefDepth
266
+ )
267
+ };
268
+ }
269
+ }
270
+ }
271
+ const result = { paths };
272
+ if (spec.info) result.info = spec.info;
273
+ if (spec.components) {
274
+ result.components = resolveRefs(
275
+ spec.components,
276
+ spec,
277
+ void 0,
278
+ maxRefDepth
279
+ );
280
+ }
281
+ return result;
282
+ }
283
+ function extractTags(spec) {
284
+ const rawPaths = spec.paths;
285
+ if (!rawPaths) return [];
286
+ const tags = /* @__PURE__ */ new Map();
287
+ for (const pathItem of Object.values(rawPaths)) {
288
+ if (!pathItem) continue;
289
+ for (const method of HTTP_METHODS) {
290
+ const op = pathItem[method];
291
+ if (op?.tags) {
292
+ for (const tag of op.tags) {
293
+ tags.set(tag, (tags.get(tag) ?? 0) + 1);
294
+ }
295
+ }
296
+ }
297
+ }
298
+ return [...tags.entries()].toSorted((a, b) => b[1] - a[1]).map(([t]) => t);
299
+ }
300
+
72
301
  // src/tools.ts
73
- function createSearchToolDefinition(toolName) {
74
- return {
75
- name: toolName,
76
- description: `Search the API specification to discover available endpoints.
302
+ var SPEC_TYPES = `
303
+ interface OperationInfo {
304
+ summary?: string;
305
+ description?: string;
306
+ tags?: string[];
307
+ operationId?: string;
308
+ parameters?: Array<{ name: string; in: string; required?: boolean; schema?: unknown; description?: string }>;
309
+ requestBody?: { required?: boolean; content?: Record<string, { schema?: unknown }> };
310
+ responses?: Record<string, { description?: string; content?: Record<string, { schema?: unknown }> }>;
311
+ }
77
312
 
78
- Write an async JavaScript arrow function that filters and explores the \`spec\` object (an OpenAPI 3.x document). The full spec is available as a global variable.
313
+ interface PathItem {
314
+ get?: OperationInfo;
315
+ post?: OperationInfo;
316
+ put?: OperationInfo;
317
+ patch?: OperationInfo;
318
+ delete?: OperationInfo;
319
+ }
79
320
 
80
- Common patterns:
81
- - Find endpoints by path: \`spec.paths\` is an object keyed by path string
82
- - Each path has HTTP methods (get, post, put, delete, patch) as keys
83
- - Each operation has: summary, description, parameters, requestBody, responses
84
- - Use spec.components.schemas for data models
321
+ declare const spec: {
322
+ paths: Record<string, PathItem>;
323
+ components?: { schemas?: Record<string, unknown> };
324
+ info?: { title?: string; version?: string; description?: string };
325
+ };
326
+ `;
327
+ function createSearchToolDefinition(toolName, context) {
328
+ const parts = [];
329
+ parts.push(
330
+ `Search the API specification to discover available endpoints. All $refs are pre-resolved inline.`
331
+ );
332
+ if (context?.tags && context.tags.length > 0) {
333
+ const shown = context.tags.slice(0, 30).join(", ");
334
+ const suffix = context.tags.length > 30 ? `... (${context.tags.length} total)` : "";
335
+ parts.push(`Tags: ${shown}${suffix}`);
336
+ }
337
+ if (context?.endpointCount) {
338
+ parts.push(`Endpoints: ${context.endpointCount}`);
339
+ }
340
+ parts.push(`Types:
341
+ ${SPEC_TYPES}`);
342
+ const exampleTag = context?.tags?.[0]?.toLowerCase() ?? "items";
343
+ parts.push(`Your code must be an async arrow function that returns the result.
85
344
 
86
345
  Examples:
87
- // Find all cluster-related endpoints
88
- async () => {
89
- return Object.entries(spec.paths)
90
- .filter(([p]) => p.includes('/clusters'))
91
- .flatMap(([path, methods]) =>
92
- Object.entries(methods)
93
- .filter(([m]) => ['get','post','put','delete','patch'].includes(m))
94
- .map(([method, op]) => ({
95
- method: method.toUpperCase(), path, summary: op.summary
96
- }))
97
- );
346
+
347
+ // List all endpoints
348
+ async () => {
349
+ const results = [];
350
+ for (const [path, methods] of Object.entries(spec.paths)) {
351
+ for (const [method, op] of Object.entries(methods)) {
352
+ results.push({ method: method.toUpperCase(), path, summary: op.summary });
353
+ }
98
354
  }
355
+ return results;
356
+ }
99
357
 
100
- // Get the schema for a specific model
101
- async () => {
102
- return spec.components?.schemas?.Product;
358
+ // Find endpoints by tag
359
+ async () => {
360
+ const results = [];
361
+ for (const [path, methods] of Object.entries(spec.paths)) {
362
+ for (const [method, op] of Object.entries(methods)) {
363
+ if (op.tags?.some(t => t.toLowerCase() === '${exampleTag}')) {
364
+ results.push({ method: method.toUpperCase(), path, summary: op.summary });
365
+ }
366
+ }
103
367
  }
368
+ return results;
369
+ }
104
370
 
105
- Return the matching endpoints/schemas as a structured result the agent can use to plan execute() calls.`,
371
+ // Get full details for a specific endpoint (refs are already resolved)
372
+ async () => {
373
+ const op = spec.paths['/example']?.get;
374
+ return { summary: op?.summary, parameters: op?.parameters, requestBody: op?.requestBody };
375
+ }`);
376
+ return {
377
+ name: toolName,
378
+ description: parts.join("\n\n"),
106
379
  inputSchema: {
107
380
  type: "object",
108
381
  properties: {
109
382
  code: {
110
383
  type: "string",
111
- description: "An async JavaScript arrow function that searches the `spec` object. Must return a value."
384
+ description: "JavaScript async arrow function to search the `spec` object"
112
385
  }
113
386
  },
114
387
  required: ["code"]
@@ -116,55 +389,67 @@ Return the matching endpoints/schemas as a structured result the agent can use t
116
389
  };
117
390
  }
118
391
  function createExecuteToolDefinition(toolName, namespace) {
119
- return {
120
- name: toolName,
121
- description: `Execute API calls by writing JavaScript code.
392
+ const types = `
393
+ interface RequestOptions {
394
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
395
+ path: string;
396
+ query?: Record<string, string | number | boolean>;
397
+ body?: unknown;
398
+ headers?: Record<string, string>;
399
+ }
122
400
 
123
- Write an async JavaScript arrow function that uses \`${namespace}.request()\` to make API calls. The request function handles authentication automatically.
401
+ interface Response<T = unknown> {
402
+ status: number;
403
+ headers: Record<string, string>;
404
+ body: T;
405
+ }
124
406
 
125
- \`${namespace}.request(options)\` takes:
126
- - method: HTTP method string ("GET", "POST", "PUT", "DELETE", "PATCH")
127
- - path: API path string (e.g. "/v1/clusters")
128
- - query: optional object of query parameters
129
- - body: optional request body (will be JSON-serialized)
130
- - headers: optional object of additional headers
407
+ declare const ${namespace}: {
408
+ request<T = unknown>(options: RequestOptions): Promise<Response<T>>;
409
+ };
410
+ `;
411
+ return {
412
+ name: toolName,
413
+ description: `Execute API calls by writing JavaScript code. First use the 'search' tool to find the right endpoints.
131
414
 
132
- Returns: { status: number, headers: object, body: unknown }
415
+ Available in your code:
416
+ ${types}
417
+ Your code must be an async arrow function that returns the result.
133
418
 
134
419
  Examples:
135
- // List resources
136
- async () => {
137
- const res = await ${namespace}.request({ method: "GET", path: "/v1/clusters" });
138
- return res.body;
139
- }
140
420
 
141
- // Create a resource
142
- async () => {
143
- return ${namespace}.request({
144
- method: "POST",
145
- path: "/v1/products",
146
- body: { name: "My Product", chart: "nginx" }
147
- });
148
- }
421
+ // List resources
422
+ async () => {
423
+ const res = await ${namespace}.request({ method: "GET", path: "/v1/items" });
424
+ return res.body;
425
+ }
149
426
 
150
- // Chain multiple calls
151
- async () => {
152
- const clusters = await ${namespace}.request({ method: "GET", path: "/v1/clusters" });
153
- const details = await Promise.all(
154
- clusters.body.map(c =>
155
- ${namespace}.request({ method: "GET", path: \`/v1/clusters/\${c.id}\` })
156
- )
157
- );
158
- return details.map(d => d.body);
159
- }
427
+ // Create a resource
428
+ async () => {
429
+ const res = await ${namespace}.request({
430
+ method: "POST",
431
+ path: "/v1/items",
432
+ body: { name: "Widget" }
433
+ });
434
+ return { status: res.status, body: res.body };
435
+ }
160
436
 
161
- Write clean, focused code. Return the data the user needs.`,
437
+ // Chain multiple calls
438
+ async () => {
439
+ const list = await ${namespace}.request({ method: "GET", path: "/v1/items" });
440
+ const details = await Promise.all(
441
+ list.body.map(item =>
442
+ ${namespace}.request({ method: "GET", path: \`/v1/items/\${item.id}\` })
443
+ )
444
+ );
445
+ return details.map(d => d.body);
446
+ }`,
162
447
  inputSchema: {
163
448
  type: "object",
164
449
  properties: {
165
450
  code: {
166
451
  type: "string",
167
- description: `An async JavaScript arrow function that uses \`${namespace}.request()\` to make API calls.`
452
+ description: `JavaScript async arrow function that uses \`${namespace}.request()\` to make API calls`
168
453
  }
169
454
  },
170
455
  required: ["code"]
@@ -172,16 +457,83 @@ Write clean, focused code. Return the data the user needs.`,
172
457
  };
173
458
  }
174
459
 
460
+ // src/truncate.ts
461
+ var CHARS_PER_TOKEN = 4;
462
+ var DEFAULT_MAX_TOKENS = 25e3;
463
+ function truncateResponse(content, maxTokens = DEFAULT_MAX_TOKENS) {
464
+ const text = typeof content === "string" ? content : JSON.stringify(content, null, 2);
465
+ const maxChars = maxTokens * CHARS_PER_TOKEN;
466
+ if (text.length <= maxChars) {
467
+ return text;
468
+ }
469
+ const truncated = text.slice(0, maxChars);
470
+ const estimatedTokens = Math.ceil(text.length / CHARS_PER_TOKEN);
471
+ return `${truncated}
472
+
473
+ --- TRUNCATED ---
474
+ Response was ~${estimatedTokens.toLocaleString()} tokens (limit: ${maxTokens.toLocaleString()}). Use more specific queries to reduce response size.`;
475
+ }
476
+
175
477
  // src/codemode.ts
478
+ var RESERVED_NAMES = /* @__PURE__ */ new Set([
479
+ "Object",
480
+ "Array",
481
+ "Promise",
482
+ "Function",
483
+ "String",
484
+ "Number",
485
+ "Boolean",
486
+ "Symbol",
487
+ "Map",
488
+ "Set",
489
+ "WeakMap",
490
+ "WeakSet",
491
+ "Date",
492
+ "RegExp",
493
+ "Error",
494
+ "JSON",
495
+ "Math",
496
+ "Proxy",
497
+ "Reflect",
498
+ "globalThis",
499
+ "undefined",
500
+ "null",
501
+ "NaN",
502
+ "Infinity",
503
+ "console",
504
+ "spec",
505
+ "global"
506
+ ]);
507
+ var VALID_JS_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
508
+ function validateNamespace(namespace) {
509
+ if (!VALID_JS_IDENTIFIER.test(namespace)) {
510
+ throw new Error(
511
+ `Invalid namespace "${namespace}": must be a valid JavaScript identifier`
512
+ );
513
+ }
514
+ if (RESERVED_NAMES.has(namespace)) {
515
+ throw new Error(
516
+ `Invalid namespace "${namespace}": conflicts with reserved name`
517
+ );
518
+ }
519
+ }
176
520
  var CodeMode = class {
177
521
  specProvider;
178
- requestBridge;
179
522
  namespace;
180
523
  executor;
181
524
  executorPromise = null;
182
525
  options;
183
526
  searchToolName;
184
527
  executeToolName;
528
+ maxResponseTokens;
529
+ // Bridge config — a fresh bridge is created per execute() call
530
+ // so the request counter resets each time.
531
+ bridgeHandler;
532
+ bridgeBaseUrl;
533
+ bridgeOptions;
534
+ // Cached processed spec & context for tool descriptions
535
+ processedSpec = null;
536
+ specContext = null;
185
537
  constructor(options) {
186
538
  this.options = options;
187
539
  this.specProvider = options.spec;
@@ -189,9 +541,15 @@ var CodeMode = class {
189
541
  this.executor = options.executor ?? null;
190
542
  this.searchToolName = "search";
191
543
  this.executeToolName = "execute";
192
- const baseUrl = options.baseUrl ?? "http://localhost";
193
- const bridge = createRequestBridge(options.request, baseUrl);
194
- this.requestBridge = (...args) => bridge(args[0]);
544
+ this.maxResponseTokens = options.maxResponseTokens ?? 25e3;
545
+ validateNamespace(this.namespace);
546
+ this.bridgeHandler = options.request;
547
+ this.bridgeBaseUrl = options.baseUrl ?? "http://localhost";
548
+ this.bridgeOptions = {
549
+ maxRequests: options.maxRequests,
550
+ maxResponseBytes: options.maxResponseBytes,
551
+ allowedHeaders: options.allowedHeaders
552
+ };
195
553
  }
196
554
  /**
197
555
  * Override the default tool names.
@@ -206,7 +564,7 @@ var CodeMode = class {
206
564
  */
207
565
  tools() {
208
566
  return [
209
- createSearchToolDefinition(this.searchToolName),
567
+ createSearchToolDefinition(this.searchToolName, this.specContext ?? void 0),
210
568
  createExecuteToolDefinition(this.executeToolName, this.namespace)
211
569
  ];
212
570
  }
@@ -228,12 +586,13 @@ var CodeMode = class {
228
586
  /**
229
587
  * Execute a search against the OpenAPI spec.
230
588
  * The code runs in a sandbox with `spec` available as a global.
589
+ * All $refs are pre-resolved inline.
231
590
  */
232
591
  async search(code) {
233
592
  const executor = await this.getExecutor();
234
- const spec = await this.resolveSpec();
593
+ const spec = await this.getProcessedSpec();
235
594
  const result = await executor.execute(code, { spec });
236
- return formatResult(result);
595
+ return this.formatResult(result);
237
596
  }
238
597
  /**
239
598
  * Execute API calls in the sandbox.
@@ -241,13 +600,18 @@ var CodeMode = class {
241
600
  */
242
601
  async execute(code) {
243
602
  const executor = await this.getExecutor();
603
+ const bridge = createRequestBridge(
604
+ this.bridgeHandler,
605
+ this.bridgeBaseUrl,
606
+ this.bridgeOptions
607
+ );
244
608
  const client = {
245
- request: this.requestBridge
609
+ request: (...args) => bridge(args[0])
246
610
  };
247
611
  const result = await executor.execute(code, {
248
612
  [this.namespace]: client
249
613
  });
250
- return formatResult(result);
614
+ return this.formatResult(result);
251
615
  }
252
616
  /**
253
617
  * Clean up sandbox resources.
@@ -261,6 +625,19 @@ var CodeMode = class {
261
625
  }
262
626
  return this.specProvider;
263
627
  }
628
+ /**
629
+ * Get the processed spec (refs resolved, fields extracted).
630
+ * Caches the result after first call.
631
+ */
632
+ async getProcessedSpec() {
633
+ if (this.processedSpec) return this.processedSpec;
634
+ const raw = await this.resolveSpec();
635
+ this.processedSpec = processSpec(raw, this.options.maxRefDepth);
636
+ const tags = extractTags(raw);
637
+ const endpointCount = Object.keys(raw.paths ?? {}).length;
638
+ this.specContext = { tags, endpointCount };
639
+ return this.processedSpec;
640
+ }
264
641
  async getExecutor() {
265
642
  if (this.executor) return this.executor;
266
643
  if (!this.executorPromise) {
@@ -273,36 +650,28 @@ var CodeMode = class {
273
650
  }
274
651
  return this.executorPromise;
275
652
  }
276
- };
277
- function formatResult(result) {
278
- if (result.error) {
279
- const parts2 = [];
280
- if (result.logs.length > 0) {
281
- parts2.push(`Console output:
282
- ${result.logs.join("\n")}`);
653
+ formatResult(result) {
654
+ if (result.error) {
655
+ return {
656
+ content: [{ type: "text", text: `Error: ${result.error}` }],
657
+ isError: true
658
+ };
283
659
  }
284
- parts2.push(`Error: ${result.error}`);
660
+ const resultText = typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2);
285
661
  return {
286
- content: [{ type: "text", text: parts2.join("\n\n") }],
287
- isError: true
662
+ content: [{ type: "text", text: truncateResponse(resultText, this.maxResponseTokens) }]
288
663
  };
289
664
  }
290
- const parts = [];
291
- if (result.logs.length > 0) {
292
- parts.push(`Console output:
293
- ${result.logs.join("\n")}`);
294
- }
295
- const resultText = typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2);
296
- parts.push(resultText);
297
- return {
298
- content: [{ type: "text", text: parts.join("\n\n") }]
299
- };
300
- }
665
+ };
301
666
  export {
302
667
  CodeMode,
303
668
  IsolatedVMExecutor,
304
- QuickJSExecutor,
305
669
  createExecutor,
306
- createRequestBridge
670
+ createRequestBridge,
671
+ extractServerBasePath,
672
+ extractTags,
673
+ processSpec,
674
+ resolveRefs,
675
+ truncateResponse
307
676
  };
308
677
  //# sourceMappingURL=index.js.map