@swifty.js/swifty 0.0.26 → 0.0.28

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 (58) hide show
  1. package/README.md +14 -6
  2. package/dist/agent-3NON2EXE.js +4 -0
  3. package/dist/anthropic-BJ5GN2VT.js +4 -0
  4. package/dist/checker-N5TIJEN5.js +4 -0
  5. package/dist/{chunk-H7VXSMJR.js → chunk-C3WGLVVZ.js} +10 -10
  6. package/dist/chunk-D34FUVGU.js +4 -0
  7. package/dist/chunk-FPQ2LBL4.js +10 -0
  8. package/dist/chunk-JGSIQXEN.js +4 -0
  9. package/dist/{chunk-5KXBVL2A.js → chunk-K5ZK27BX.js} +1 -1
  10. package/dist/chunk-LZBOXJX2.js +301 -0
  11. package/dist/chunk-OTVZGPHD.js +121 -0
  12. package/dist/chunk-QCKJLO6X.js +4 -0
  13. package/dist/chunk-R63ASIIW.js +407 -0
  14. package/dist/chunk-WX3B64R4.js +4 -0
  15. package/dist/chunk-Y6SQB5FG.js +4 -0
  16. package/dist/{devtools-3OAJUZGE.js → devtools-OJ75EP3T.js} +1 -1
  17. package/dist/glob.wasm +0 -0
  18. package/dist/lib/agent-XYO2MXXK.js +11 -0
  19. package/dist/lib/{anthropic-RYPJDHQM.js → anthropic-TGJGAP5R.js} +4 -5
  20. package/dist/lib/{checker-6BW4222R.js → checker-AXHPKVBY.js} +2 -2
  21. package/dist/lib/{chunk-HK2Z6WP4.js → chunk-2IVEVG5N.js} +5 -1
  22. package/dist/lib/{chunk-2AUSNVIB.js → chunk-ALJAVRRX.js} +28 -34
  23. package/dist/lib/{chunk-GNI7YX6F.js → chunk-DJ6AILPN.js} +70 -24
  24. package/dist/lib/chunk-DV6RV5WU.js +2345 -0
  25. package/dist/lib/{chunk-3LU4APFB.js → chunk-FBSPZQGC.js} +126 -73
  26. package/dist/lib/chunk-IB5IQK2S.js +136 -0
  27. package/dist/lib/{chunk-PZ42NAFA.js → chunk-KG4MJGKQ.js} +19 -21
  28. package/dist/lib/{chunk-XFSN4LMA.js → chunk-P4F46PEF.js} +43 -14
  29. package/dist/lib/glob.wasm +0 -0
  30. package/dist/lib/index.d.ts +145 -123
  31. package/dist/lib/index.js +2186 -1726
  32. package/dist/lib/{openai-VX5VBXZ4.js → openai-5UCIIDPO.js} +2 -3
  33. package/dist/lib/{tool-filter-VF7TZRE5.js → tool-filter-R6HDDJHE.js} +1 -1
  34. package/dist/main.js +226 -196
  35. package/dist/{node-OES65R33.js → node-4LTSRXL2.js} +1 -1
  36. package/dist/{openai-6MXNADYN.js → openai-YLS2LUAI.js} +14 -14
  37. package/dist/server-GX72MJQF.js +244 -0
  38. package/dist/{tool-filter-NO7I3HFD.js → tool-filter-VBP6WOGO.js} +1 -1
  39. package/package.json +36 -32
  40. package/dist/agent-GAABBZRW.js +0 -4
  41. package/dist/anthropic-7QRAPLOY.js +0 -4
  42. package/dist/checker-3RWAYF2U.js +0 -4
  43. package/dist/chunk-7MHXMDYC.js +0 -35
  44. package/dist/chunk-A2VR3BC4.js +0 -4
  45. package/dist/chunk-FZPTNGTU.js +0 -4
  46. package/dist/chunk-MWZIARZE.js +0 -130
  47. package/dist/chunk-NR62AA5K.js +0 -4
  48. package/dist/chunk-QN27QEFS.js +0 -4
  49. package/dist/chunk-RMMK4PO5.js +0 -389
  50. package/dist/chunk-SIWLMWHH.js +0 -93
  51. package/dist/chunk-VC6OXOMP.js +0 -238
  52. package/dist/chunk-W37Y53LX.js +0 -4
  53. package/dist/chunk-XBHVVB5A.js +0 -10
  54. package/dist/lib/agent-U7MIUCKT.js +0 -9
  55. package/dist/lib/chunk-2QILP24G.js +0 -1120
  56. package/dist/lib/chunk-OO2CLOEE.js +0 -88
  57. package/dist/lib/chunk-RKJYTYQM.js +0 -1285
  58. package/dist/server-P2VE5HLJ.js +0 -244
@@ -2,6 +2,40 @@ import {
2
2
  createChildLogger
3
3
  } from "./chunk-EY7HE52Q.js";
4
4
 
5
+ // src/llm/errors.ts
6
+ var LLMError = class extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "LLMError";
10
+ }
11
+ };
12
+ var AuthenticationError = class extends LLMError {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = "AuthenticationError";
16
+ }
17
+ };
18
+ var RateLimitError = class extends LLMError {
19
+ retryAfter;
20
+ constructor(message, retryAfter) {
21
+ super(message);
22
+ this.name = "RateLimitError";
23
+ this.retryAfter = retryAfter;
24
+ }
25
+ };
26
+ var NetworkError = class extends LLMError {
27
+ constructor(message) {
28
+ super(message);
29
+ this.name = "NetworkError";
30
+ }
31
+ };
32
+ var ContextTooLongError = class extends LLMError {
33
+ constructor(message) {
34
+ super(message);
35
+ this.name = "ContextTooLongError";
36
+ }
37
+ };
38
+
5
39
  // src/config/config.ts
6
40
  import { existsSync, readFileSync } from "fs";
7
41
  import { homedir } from "os";
@@ -38,68 +72,22 @@ var ProviderConfigSchema = z.object({
38
72
  context_window: z.coerce.number().optional(),
39
73
  max_output_tokens: z.coerce.number().optional()
40
74
  });
41
- var MODEL_CONTEXT_WINDOWS = [
42
- // 1M-token variants (e.g. "...-1m") come first so they win over the base family.
43
- ["1m", 1e6],
44
- ["gpt-4.1", 1e6],
45
- ["gpt-4o", 128e3],
46
- ["gpt-4-turbo", 128e3],
47
- ["o1", 2e5],
48
- ["o3", 2e5],
49
- ["o4", 2e5],
50
- ["gpt-3.5", 16385],
51
- ["claude", 2e5]
52
- ];
53
- function lookupModelContextWindow(model) {
54
- const model_ = model.toLowerCase();
55
- for (const [m, window] of MODEL_CONTEXT_WINDOWS) {
56
- if (model_.includes(m)) {
57
- return window;
58
- }
59
- }
60
- return model_.includes("claude") ? 2e5 : 128e3;
61
- }
62
- function getContextWindow(p) {
63
- if (p.context_window && p.context_window > 0) {
64
- return p.context_window;
65
- }
66
- return lookupModelContextWindow(p.model);
67
- }
68
- var fetchedWindowCache = /* @__PURE__ */ new Map();
69
- async function getContextWindowAsync(p, fetcher) {
70
- if (p.context_window && p.context_window > 0) {
71
- return p.context_window;
72
- }
73
- if (p.protocol === "anthropic") {
74
- const key = `${p.name}-${p.model}`;
75
- let fetched = fetchedWindowCache.get(key);
76
- if (fetched === void 0) {
77
- try {
78
- const fn = fetcher ?? (await import("./anthropic-RYPJDHQM.js")).fetchModelContextWindow;
79
- fetched = await fn(p);
80
- } catch (err) {
81
- log.error({ err }, "config operation failed");
82
- fetched = 0;
83
- }
84
- fetchedWindowCache.set(key, fetched);
85
- }
86
- if (fetched && fetched > 0) {
87
- return fetched;
88
- }
89
- }
90
- return lookupModelContextWindow(p.model);
75
+ var DEFAULT_PROVIDER_THINKING = true;
76
+ var DEFAULT_CONTEXT_WINDOW = 1e6;
77
+ var DEFAULT_MAX_OUTPUT_TOKENS = 128e3;
78
+ function withProviderDefaults(provider) {
79
+ return {
80
+ ...provider,
81
+ thinking: provider.thinking ?? DEFAULT_PROVIDER_THINKING,
82
+ context_window: getContextWindow(provider),
83
+ max_output_tokens: getMaxOutputTokens(provider)
84
+ };
91
85
  }
92
- function _resetContextWindowCache() {
93
- fetchedWindowCache.clear();
86
+ function getContextWindow(provider) {
87
+ return Number.isSafeInteger(provider.context_window) && (provider.context_window ?? 0) > 0 ? provider.context_window ?? DEFAULT_CONTEXT_WINDOW : DEFAULT_CONTEXT_WINDOW;
94
88
  }
95
- function getMaxOutputTokens(p) {
96
- if (p.max_output_tokens && p.max_output_tokens > 0) {
97
- return p.max_output_tokens;
98
- }
99
- if (p.thinking) {
100
- return 64e3;
101
- }
102
- return 8192;
89
+ function getMaxOutputTokens(provider) {
90
+ return Number.isSafeInteger(provider.max_output_tokens) && (provider.max_output_tokens ?? 0) > 0 ? provider.max_output_tokens ?? DEFAULT_MAX_OUTPUT_TOKENS : DEFAULT_MAX_OUTPUT_TOKENS;
103
91
  }
104
92
  function resolveAPIKey(p) {
105
93
  if (p.api_key) {
@@ -173,10 +161,8 @@ function loadSingleFile(path) {
173
161
  if (parsed.success) {
174
162
  const data2 = parsed.data;
175
163
  return {
176
- providers: data2.providers,
177
- permission_mode: data2.permission_mode,
178
- mcp_servers: data2.mcp_servers,
179
- hooks: data2.hooks
164
+ ...data2,
165
+ providers: data2.providers.map(withProviderDefaults)
180
166
  };
181
167
  }
182
168
  log.error({ error: parsed.error }, "config error");
@@ -190,7 +176,7 @@ function loadSingleFile(path) {
190
176
  if ("providers" in raw) {
191
177
  const parsed2 = safeParse(z.array(ProviderConfigSchema), raw.providers);
192
178
  if (parsed2.success) {
193
- providers = parsed2.data;
179
+ providers = parsed2.data.map(withProviderDefaults);
194
180
  }
195
181
  }
196
182
  if ("permission_mode" in raw && typeof raw.permission_mode === "string") {
@@ -278,7 +264,7 @@ function validateProviders(config) {
278
264
  base_url: p.base_url,
279
265
  model: p.model
280
266
  };
281
- const missing = requiredFields.filter((field) => !(field in values));
267
+ const missing = requiredFields.filter((field) => !values[field].trim());
282
268
  if (missing.length > 0) {
283
269
  throw new ConfigError(`Provider #${String(i + 1)}: missing fields: ${missing.join(", ")}`);
284
270
  }
@@ -289,10 +275,12 @@ function validateProviders(config) {
289
275
  }
290
276
  }
291
277
  }
292
- function loadConfig(path) {
278
+ function loadConfig(path, options = {}) {
293
279
  if (path) {
294
280
  const config = loadSingleFile(path);
295
- validateProviders(config);
281
+ if (!options.allowEmptyProviders || config.providers.length > 0) {
282
+ validateProviders(config);
283
+ }
296
284
  return config;
297
285
  }
298
286
  const wd = process.cwd();
@@ -315,25 +303,90 @@ function loadConfig(path) {
315
303
  }
316
304
  }
317
305
  if (!merged) {
306
+ if (options.allowEmptyProviders) {
307
+ return { providers: [], mcp_servers: [], hooks: [] };
308
+ }
318
309
  throw new ConfigError(
319
310
  "No config file found, expected .swifty/config.y(a)ml under project or $HOME/.swifty/config.y(a)ml."
320
311
  );
321
312
  }
322
- validateProviders(merged);
313
+ if (!options.allowEmptyProviders || merged.providers.length > 0) {
314
+ validateProviders(merged);
315
+ }
323
316
  return merged;
324
317
  }
325
318
 
319
+ // src/conversation/pairing.ts
320
+ var INTERRUPTED_TOOL_RESULT = "Tool execution was interrupted. The tool may or may not have completed; verify before relying on its effects.";
321
+ var REJECTED_TOOL_RESULT = "The user rejected this tool use. Nothing was changed (for file edits, the new content was NOT written).";
322
+ function ensureToolPairing(messages) {
323
+ const out = [];
324
+ for (let i = 0; i < messages.length; i++) {
325
+ const m = messages[i];
326
+ if (m.role === "assistant" && m.toolUses?.length) {
327
+ out.push(m);
328
+ const pending = new Set(m.toolUses.map((tu) => tu.toolUseId));
329
+ const results = [];
330
+ const resultMessages = [];
331
+ while (i + 1 < messages.length) {
332
+ const next = messages[i + 1];
333
+ if (next.role !== "user" || !next.toolResults?.length || next.toolUses?.length) {
334
+ break;
335
+ }
336
+ i++;
337
+ resultMessages.push(next);
338
+ for (const tr of next.toolResults) {
339
+ if (pending.delete(tr.toolUseId)) {
340
+ results.push(tr);
341
+ }
342
+ }
343
+ }
344
+ for (const toolUseId of pending) {
345
+ results.push({ toolUseId, content: INTERRUPTED_TOOL_RESULT, isError: true });
346
+ }
347
+ out.push({
348
+ ...resultMessages[0] ?? { role: "user", content: "" },
349
+ toolResults: results
350
+ });
351
+ for (const remaining of resultMessages.slice(1)) {
352
+ if (remaining.content.length > 0 || remaining.thinkingBlocks?.length) {
353
+ out.push({ ...remaining, toolResults: [] });
354
+ }
355
+ }
356
+ continue;
357
+ }
358
+ if ((m.toolResults?.length ?? 0) > 0) {
359
+ if (m.content.length === 0 && !m.toolUses?.length && !m.thinkingBlocks?.length) {
360
+ continue;
361
+ }
362
+ out.push({ ...m, toolResults: [] });
363
+ } else {
364
+ out.push(m);
365
+ }
366
+ }
367
+ return out;
368
+ }
369
+
326
370
  export {
371
+ LLMError,
372
+ AuthenticationError,
373
+ RateLimitError,
374
+ NetworkError,
375
+ ContextTooLongError,
327
376
  ConfigError,
328
377
  ProviderConfigSchema,
329
- lookupModelContextWindow,
378
+ DEFAULT_PROVIDER_THINKING,
379
+ DEFAULT_CONTEXT_WINDOW,
380
+ DEFAULT_MAX_OUTPUT_TOKENS,
381
+ withProviderDefaults,
330
382
  getContextWindow,
331
- getContextWindowAsync,
332
- _resetContextWindowCache,
333
383
  getMaxOutputTokens,
334
384
  resolveAPIKey,
335
385
  HookConfigSchema,
336
386
  forkEnabled,
337
387
  mergeConfig,
338
- loadConfig
388
+ loadConfig,
389
+ INTERRUPTED_TOOL_RESULT,
390
+ REJECTED_TOOL_RESULT,
391
+ ensureToolPairing
339
392
  };
@@ -0,0 +1,136 @@
1
+ // src/tools/types.ts
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ var IMAGE_MEDIA_TYPES = /* @__PURE__ */ new Set([
6
+ "image/jpeg",
7
+ "image/png",
8
+ "image/gif",
9
+ "image/webp"
10
+ ]);
11
+ function isImageMediaType(value) {
12
+ return IMAGE_MEDIA_TYPES.has(value);
13
+ }
14
+ function normalizeTextBlock(value) {
15
+ return isRecord(value) && value.type === "text" && typeof value.text === "string" ? { type: "text", text: value.text } : null;
16
+ }
17
+ function normalizeImageBlock(value) {
18
+ if (!isRecord(value) || value.type !== "image" || !isRecord(value.source)) {
19
+ return null;
20
+ }
21
+ if (value.source.type === "url" && typeof value.source.url === "string") {
22
+ return { type: "image", source: { type: "url", url: value.source.url } };
23
+ }
24
+ if (value.source.type === "base64" && typeof value.source.media_type === "string" && isImageMediaType(value.source.media_type) && typeof value.source.data === "string") {
25
+ return {
26
+ type: "image",
27
+ source: {
28
+ type: "base64",
29
+ media_type: value.source.media_type,
30
+ data: value.source.data
31
+ }
32
+ };
33
+ }
34
+ return null;
35
+ }
36
+ function normalizeDocumentBlock(value) {
37
+ if (value.type !== "document" || !isRecord(value.source)) {
38
+ return null;
39
+ }
40
+ let source = null;
41
+ if (value.source.type === "url" && typeof value.source.url === "string") {
42
+ source = { type: "url", url: value.source.url };
43
+ } else if (value.source.type === "base64" && value.source.media_type === "application/pdf" && typeof value.source.data === "string") {
44
+ source = { type: "base64", media_type: "application/pdf", data: value.source.data };
45
+ } else if (value.source.type === "text" && value.source.media_type === "text/plain" && typeof value.source.data === "string") {
46
+ source = { type: "text", media_type: "text/plain", data: value.source.data };
47
+ } else if (value.source.type === "content") {
48
+ if (typeof value.source.content === "string") {
49
+ source = { type: "content", content: value.source.content };
50
+ } else if (Array.isArray(value.source.content)) {
51
+ const content = [];
52
+ for (const raw of value.source.content) {
53
+ const block = normalizeTextBlock(raw) ?? normalizeImageBlock(raw);
54
+ if (!block) {
55
+ return null;
56
+ }
57
+ content.push(block);
58
+ }
59
+ source = { type: "content", content };
60
+ }
61
+ }
62
+ if (!source) {
63
+ return null;
64
+ }
65
+ return {
66
+ type: "document",
67
+ source,
68
+ ...typeof value.title === "string" || value.title === null ? { title: value.title } : {},
69
+ ...typeof value.context === "string" || value.context === null ? { context: value.context } : {}
70
+ };
71
+ }
72
+ function normalizeToolResultContentBlock(value) {
73
+ const text = normalizeTextBlock(value);
74
+ if (text) {
75
+ return text;
76
+ }
77
+ const image = normalizeImageBlock(value);
78
+ if (image) {
79
+ return image;
80
+ }
81
+ if (!isRecord(value)) {
82
+ return null;
83
+ }
84
+ if (value.type === "tool_reference" && typeof value.tool_name === "string") {
85
+ return { type: "tool_reference", tool_name: value.tool_name };
86
+ }
87
+ if (value.type === "search_result" && typeof value.source === "string" && typeof value.title === "string" && Array.isArray(value.content)) {
88
+ const content = [];
89
+ for (const raw of value.content) {
90
+ const block = normalizeTextBlock(raw);
91
+ if (!block) {
92
+ return null;
93
+ }
94
+ content.push(block);
95
+ }
96
+ return { type: "search_result", source: value.source, title: value.title, content };
97
+ }
98
+ return normalizeDocumentBlock(value);
99
+ }
100
+ function isToolResultContentBlock(value) {
101
+ return normalizeToolResultContentBlock(value) !== null;
102
+ }
103
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
104
+ ".claude",
105
+ // Claude Code
106
+ ".git",
107
+ // Git
108
+ ".swifty",
109
+ // Swifty
110
+ ".next",
111
+ // Next.js
112
+ ".venv",
113
+ // Python venv
114
+ ".mypy_cache",
115
+ // Python mypy
116
+ ".tox",
117
+ // Python tox
118
+ "__pycache__",
119
+ // Python
120
+ "build",
121
+ // C++
122
+ "dist",
123
+ // Webpack, Vite
124
+ "node_modules",
125
+ // Node.js
126
+ "vendor",
127
+ // Go
128
+ "venv"
129
+ // Python venv
130
+ ]);
131
+
132
+ export {
133
+ normalizeToolResultContentBlock,
134
+ isToolResultContentBlock,
135
+ SKIP_DIRS
136
+ };
@@ -54,9 +54,9 @@ var MCPToolWrapper = class {
54
54
  input_schema: this.inputSchema
55
55
  };
56
56
  }
57
- async execute(_ctx, args) {
57
+ async execute(ctx, args) {
58
58
  try {
59
- return await this.client.callTool(this.originalName, args);
59
+ return await this.client.callTool(this.originalName, args, ctx.abortSignal);
60
60
  } catch (err) {
61
61
  log.error({ err }, "mcp operation failed");
62
62
  return {
@@ -82,7 +82,7 @@ function coerceScalar(value, want) {
82
82
  return value;
83
83
  }
84
84
  const parsed = want === "integer" ? Number.parseInt(text, 10) : Number.parseFloat(text);
85
- return Number.isNaN(parsed) ? value : parsed;
85
+ return Number.isFinite(parsed) ? parsed : value;
86
86
  }
87
87
  if (want === "boolean" && typeof value === "string") {
88
88
  const low = value.trim().toLowerCase();
@@ -177,21 +177,15 @@ var McpCallTool = class {
177
177
  }
178
178
  };
179
179
  }
180
- /**
181
- * Try in order: full name / server+short name / unique short-name suffix match.
182
- *
183
- * The model very often passes only the short name (roughly three in ten calls in
184
- * practice), so this must be tolerant — otherwise it needlessly costs a retry
185
- * round.
186
- */
187
- resolve(server, tool) {
188
- const direct = this.registry.get(tool) ?? this.registry.get(buildMcpToolName(server, tool));
189
- if (direct) {
190
- return direct;
180
+ resolveTarget(args) {
181
+ const server = strArg(args, "server");
182
+ const name = strArg(args, "tool");
183
+ if (!server || !name) {
184
+ return void 0;
191
185
  }
192
- const suffix = MCP_NAME_SEP + sanitizeSegment(tool);
193
- const matches = this.registry.listTools().filter((t) => t.name.startsWith(MCP_TOOL_PREFIX) && t.name.endsWith(suffix));
194
- return matches.length === 1 ? matches[0] : void 0;
186
+ const fullName = name.startsWith(MCP_TOOL_PREFIX) ? name : buildMcpToolName(server, name);
187
+ const target = this.registry.get(fullName);
188
+ return target && isMcpToolLike(target) && sanitizeSegment(target.mcpServerName) === sanitizeSegment(server) ? target : void 0;
195
189
  }
196
190
  availableNames() {
197
191
  return this.registry.listTools().filter((t) => t.name.startsWith(MCP_TOOL_PREFIX)).map((t) => t.name).sort();
@@ -202,7 +196,7 @@ var McpCallTool = class {
202
196
  if (tool === "") {
203
197
  return { output: "McpCall requires a 'tool' name", isError: true };
204
198
  }
205
- const target = this.resolve(server, tool);
199
+ const target = this.resolveTarget(args);
206
200
  if (!target) {
207
201
  const names = this.availableNames();
208
202
  const hint = names.length > 0 ? names.join(", ") : "(none connected)";
@@ -211,10 +205,13 @@ var McpCallTool = class {
211
205
  isError: true
212
206
  };
213
207
  }
214
- let inner = {};
215
- if (typeof args.arguments === "object" && args.arguments !== null && !Array.isArray(args.arguments)) {
216
- inner = asRecord(args.arguments);
208
+ if (typeof args.arguments !== "object" || args.arguments === null || Array.isArray(args.arguments)) {
209
+ return {
210
+ output: "McpCall requires an 'arguments' object matching the target schema",
211
+ isError: true
212
+ };
217
213
  }
214
+ let inner = asRecord(args.arguments);
218
215
  if (isMcpToolLike(target)) {
219
216
  const schema = target.mcpInputSchema();
220
217
  if (Object.keys(schema).length > 0) {
@@ -224,6 +221,7 @@ var McpCallTool = class {
224
221
  }
225
222
  }
226
223
  }
224
+ ctx.abortSignal?.throwIfAborted();
227
225
  return target.execute(ctx, inner);
228
226
  }
229
227
  };
@@ -1,15 +1,13 @@
1
- import {
2
- getMaxOutputTokens,
3
- resolveAPIKey
4
- } from "./chunk-3LU4APFB.js";
5
1
  import {
6
2
  AuthenticationError,
7
3
  ContextTooLongError,
8
4
  LLMError,
9
5
  NetworkError,
10
6
  RateLimitError,
11
- ensureToolPairing
12
- } from "./chunk-OO2CLOEE.js";
7
+ ensureToolPairing,
8
+ getMaxOutputTokens,
9
+ resolveAPIKey
10
+ } from "./chunk-FBSPZQGC.js";
13
11
  import {
14
12
  asRecord,
15
13
  asString,
@@ -82,6 +80,7 @@ var OpenAIClient = class {
82
80
  let jsonAccumulate = "";
83
81
  let reasoningId = "";
84
82
  let reasoningText = "";
83
+ let sawTerminalResponse = false;
85
84
  for await (const event of stream) {
86
85
  if (event.type === "response.output_text.delta") {
87
86
  yield {
@@ -139,19 +138,22 @@ var OpenAIClient = class {
139
138
  currentToolId = "";
140
139
  jsonAccumulate = "";
141
140
  }
142
- } else if (event.type === "response.completed") {
141
+ } else if (event.type === "response.completed" || event.type === "response.incomplete") {
142
+ sawTerminalResponse = true;
143
143
  const usage = event.response.usage;
144
144
  if (usage) {
145
145
  outputTokens = usage.output_tokens;
146
- cacheReadInputTokens = usage.input_tokens_details.cached_tokens;
146
+ cacheReadInputTokens = usage.input_tokens_details?.cached_tokens ?? 0;
147
147
  inputTokens = Math.max(0, usage.input_tokens - cacheReadInputTokens);
148
148
  }
149
149
  let stopReason = "end_turn";
150
150
  const resp = event.response;
151
- if (resp.status === "incomplete") {
151
+ if (event.type === "response.incomplete" || resp.status === "incomplete") {
152
152
  const details = resp.incomplete_details;
153
153
  if (details?.reason === "max_output_tokens") {
154
154
  stopReason = "max_tokens";
155
+ } else {
156
+ throw new LLMError(`Response incomplete: ${details?.reason ?? "unknown reason"}`);
155
157
  }
156
158
  }
157
159
  yield {
@@ -165,8 +167,18 @@ var OpenAIClient = class {
165
167
  // 0
166
168
  }
167
169
  };
170
+ } else if (event.type === "response.failed") {
171
+ const error = event.response.error;
172
+ const message = `${error?.code ?? "unknown"}: ${error?.message ?? "Response failed"}`;
173
+ throw containsContextLengthError(message) ? new ContextTooLongError(message) : new LLMError(message);
174
+ } else if (event.type === "error") {
175
+ const message = `${event.code ?? "unknown"}: ${event.message}`;
176
+ throw containsContextLengthError(message) ? new ContextTooLongError(message) : new LLMError(message);
168
177
  }
169
178
  }
179
+ if (!sawTerminalResponse) {
180
+ throw new NetworkError("OpenAI Responses stream ended before a terminal response event");
181
+ }
170
182
  } catch (err) {
171
183
  log.error({ err }, "llm operation failed");
172
184
  throw classifyOpenAIError(err);
@@ -349,6 +361,9 @@ function buildOpenAIInput(messages) {
349
361
  output: toolOutputForResponses(tr)
350
362
  });
351
363
  }
364
+ if (m.content.length > 0) {
365
+ result.push({ role: "user", content: userContentsFor(m.content) });
366
+ }
352
367
  } else if (m.role === "assistant") {
353
368
  result.push({
354
369
  role: "assistant",
@@ -510,6 +525,9 @@ var OpenAICompatClient = class {
510
525
  }
511
526
  }
512
527
  }
528
+ if (finishReason === null) {
529
+ throw new NetworkError("Chat Completions stream ended before a finish reason");
530
+ }
513
531
  let stopReason;
514
532
  if (finishReason === "length") {
515
533
  stopReason = "max_tokens";
@@ -535,6 +553,9 @@ var OpenAICompatClient = class {
535
553
  }
536
554
  };
537
555
  function classifyOpenAIError(err) {
556
+ if (err instanceof LLMError) {
557
+ return err;
558
+ }
538
559
  if (err instanceof OpenAI.APIError) {
539
560
  if (err.status === 413 /* PromptTooLong */ || err.status === 400 /* BadRequest */ && containsContextLengthError(err.message)) {
540
561
  return new ContextTooLongError(`Context Too Long: ${err.message}`);
@@ -543,7 +564,11 @@ function classifyOpenAIError(err) {
543
564
  return new AuthenticationError(`Invalid API key: ${err.message}`);
544
565
  }
545
566
  if (err.status === 429 /* RateLimitError */) {
546
- return new RateLimitError(`Rate limit error, please wait.`);
567
+ const headers = err.headers;
568
+ return new RateLimitError(
569
+ "Rate limit error, please wait.",
570
+ headers instanceof Headers ? headers.get("retry-after") ?? void 0 : void 0
571
+ );
547
572
  }
548
573
  return new LLMError(`OpenAI API error (${asString(err.status)}): ${err.message}`);
549
574
  }
@@ -558,16 +583,14 @@ function buildChatCompletionMessages(messages) {
558
583
  params.push({
559
584
  role: "assistant",
560
585
  content: assistantText || null,
586
+ ...reasoning ? { reasoning_content: reasoning } : {},
561
587
  tool_calls: m.toolUses.map((tu) => ({
562
588
  id: tu.toolUseId,
563
589
  type: "function",
564
590
  function: {
565
591
  name: tu.toolName,
566
592
  arguments: JSON.stringify(tu.arguments)
567
- },
568
- ...reasoning ? {
569
- reasoning_content: reasoning
570
- } : {}
593
+ }
571
594
  }))
572
595
  });
573
596
  } else if (m.toolResults && m.toolResults.length > 0) {
@@ -580,6 +603,12 @@ function buildChatCompletionMessages(messages) {
580
603
  });
581
604
  pendingRichParts.push(...collectRichParts(tr));
582
605
  }
606
+ if (m.content.length > 0) {
607
+ const content = userPartsFor(m.content);
608
+ pendingRichParts.push(
609
+ ...typeof content === "string" ? [{ type: "text", text: content }] : content
610
+ );
611
+ }
583
612
  if (pendingRichParts.length > 0) {
584
613
  params.push({
585
614
  role: "user",
Binary file