@kkeve/suite 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.
Files changed (63) hide show
  1. package/dist/agent/factory.d.ts +25 -0
  2. package/dist/agent/factory.js +99 -0
  3. package/dist/agent/index.d.ts +3 -0
  4. package/dist/agent/index.js +7 -0
  5. package/dist/agent/loader.d.ts +22 -0
  6. package/dist/agent/loader.js +117 -0
  7. package/dist/agent/types.d.ts +9 -0
  8. package/dist/agent/types.js +2 -0
  9. package/dist/agent-loop/complete-task.d.ts +28 -0
  10. package/dist/agent-loop/complete-task.js +70 -0
  11. package/dist/agent-loop/index.d.ts +16 -0
  12. package/dist/agent-loop/index.js +26 -0
  13. package/dist/agent-loop/loop.d.ts +42 -0
  14. package/dist/agent-loop/loop.js +282 -0
  15. package/dist/agent-loop/runtime.d.ts +40 -0
  16. package/dist/agent-loop/runtime.js +163 -0
  17. package/dist/agent-loop/types.d.ts +161 -0
  18. package/dist/agent-loop/types.js +105 -0
  19. package/dist/context/index.d.ts +8 -0
  20. package/dist/context/index.js +29 -0
  21. package/dist/context/tokenCalculation.d.ts +55 -0
  22. package/dist/context/tokenCalculation.js +95 -0
  23. package/dist/context/truncation.d.ts +67 -0
  24. package/dist/context/truncation.js +142 -0
  25. package/dist/context/types.d.ts +7 -0
  26. package/dist/context/types.js +27 -0
  27. package/dist/index.d.ts +23 -0
  28. package/dist/index.js +45 -0
  29. package/dist/llm/OpenAIClient.d.ts +31 -0
  30. package/dist/llm/OpenAIClient.js +455 -0
  31. package/dist/llm/constants.d.ts +2 -0
  32. package/dist/llm/constants.js +6 -0
  33. package/dist/llm/errors.d.ts +25 -0
  34. package/dist/llm/errors.js +51 -0
  35. package/dist/llm/index.d.ts +49 -0
  36. package/dist/llm/index.js +158 -0
  37. package/dist/llm/types.d.ts +124 -0
  38. package/dist/llm/types.js +2 -0
  39. package/dist/llm/utils.d.ts +29 -0
  40. package/dist/llm/utils.js +284 -0
  41. package/dist/skill/frontmatter-parser.d.ts +50 -0
  42. package/dist/skill/frontmatter-parser.js +152 -0
  43. package/dist/skill/index.d.ts +12 -0
  44. package/dist/skill/index.js +17 -0
  45. package/dist/skill/loader.d.ts +96 -0
  46. package/dist/skill/loader.js +256 -0
  47. package/dist/tools/ask-user.d.ts +8 -0
  48. package/dist/tools/ask-user.js +47 -0
  49. package/dist/tools/bash.d.ts +47 -0
  50. package/dist/tools/bash.js +213 -0
  51. package/dist/tools/edit-file.d.ts +9 -0
  52. package/dist/tools/edit-file.js +76 -0
  53. package/dist/tools/glob.d.ts +8 -0
  54. package/dist/tools/glob.js +101 -0
  55. package/dist/tools/grep.d.ts +10 -0
  56. package/dist/tools/grep.js +76 -0
  57. package/dist/tools/index.d.ts +24 -0
  58. package/dist/tools/index.js +47 -0
  59. package/dist/tools/read-files.d.ts +8 -0
  60. package/dist/tools/read-files.js +81 -0
  61. package/dist/tools/write-file.d.ts +8 -0
  62. package/dist/tools/write-file.js +63 -0
  63. package/package.json +59 -0
@@ -0,0 +1,455 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.OpenAIClient = void 0;
37
+ /**
38
+ * OpenAI Client implementation
39
+
40
+ 不需要工具 → LLM.chat() → 直接调 API,不带 tools
41
+ 需要工具循环 → LLM.chatWithTools() → 调 OpenAIClient.chat(),tool_choice: auto
42
+ 需要强制工具 → LLM.invoke() → 调 OpenAIClient.invoke(),tool_choice: required
43
+
44
+ */
45
+ const z = __importStar(require("zod/v4"));
46
+ const errors_1 = require("./errors");
47
+ const utils_1 = require("./utils");
48
+ /**
49
+ * Client for OpenAI compatible APIs
50
+ */
51
+ class OpenAIClient {
52
+ config;
53
+ fetch;
54
+ constructor(config) {
55
+ this.config = config;
56
+ this.fetch = config.customFetch;
57
+ }
58
+ async invoke(messages, tools, abortSignal, options) {
59
+ // 1. Convert tools to OpenAI format
60
+ // If a Tool has customJsonSchema, use it directly (detailed schema for LLM)
61
+ // Otherwise, convert from zod inputSchema via zodToOpenAITool
62
+ const openaiTools = Object.entries(tools).map(([name, t]) => {
63
+ if (t.customJsonSchema) {
64
+ return {
65
+ type: 'function',
66
+ function: {
67
+ name,
68
+ description: t.description,
69
+ parameters: t.customJsonSchema,
70
+ },
71
+ };
72
+ }
73
+ return (0, utils_1.zodToOpenAITool)(name, t);
74
+ });
75
+ // Build request body
76
+ let toolChoice = 'required';
77
+ if (options?.toolChoiceName && !this.config.disableNamedToolChoice) {
78
+ toolChoice = { type: 'function', function: { name: options.toolChoiceName } };
79
+ }
80
+ else if (this.config.disableNamedToolChoice) {
81
+ toolChoice = undefined;
82
+ }
83
+ const requestBody = {
84
+ model: this.config.model,
85
+ temperature: this.config.temperature,
86
+ messages,
87
+ tools: openaiTools,
88
+ parallel_tool_calls: false,
89
+ ...(toolChoice !== undefined && { tool_choice: toolChoice }),
90
+ };
91
+ (0, utils_1.modelPatch)(requestBody);
92
+ // 2. Call API
93
+ let response;
94
+ try {
95
+ response = await this.fetch(`${this.config.baseURL}/chat/completions`, {
96
+ method: 'POST',
97
+ headers: {
98
+ 'Content-Type': 'application/json',
99
+ ...(this.config.apiKey && { Authorization: `Bearer ${this.config.apiKey}` }),
100
+ },
101
+ body: JSON.stringify(requestBody),
102
+ signal: abortSignal,
103
+ });
104
+ }
105
+ catch (error) {
106
+ const isAbortError = error?.name === 'AbortError';
107
+ const errorMessage = isAbortError ? 'Network request aborted' : 'Network request failed';
108
+ if (!isAbortError)
109
+ console.error(error);
110
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.NETWORK_ERROR, errorMessage, error);
111
+ }
112
+ // 3. Handle HTTP errors
113
+ if (!response.ok) {
114
+ const errorData = await response.json().catch();
115
+ const errorMessage = errorData.error?.message || response.statusText;
116
+ if (response.status === 401 || response.status === 403) {
117
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.AUTH_ERROR, `Authentication failed: ${errorMessage}`, errorData);
118
+ }
119
+ if (response.status === 429) {
120
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.RATE_LIMIT, `Rate limit exceeded: ${errorMessage}`, errorData);
121
+ }
122
+ if (response.status >= 500) {
123
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.SERVER_ERROR, `Server error: ${errorMessage}`, errorData);
124
+ }
125
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.UNKNOWN, `HTTP ${response.status}: ${errorMessage}`, errorData);
126
+ }
127
+ // 4. Parse and validate response
128
+ const data = await response.json();
129
+ const choice = data.choices?.[0];
130
+ if (!choice) {
131
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.UNKNOWN, 'No choices in response', data);
132
+ }
133
+ // Check finish_reason
134
+ switch (choice.finish_reason) {
135
+ case 'tool_calls':
136
+ case 'function_call':
137
+ case 'stop':
138
+ break;
139
+ case 'length':
140
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.CONTEXT_LENGTH, 'Response truncated: max tokens reached', undefined, data);
141
+ case 'content_filter':
142
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.CONTENT_FILTER, 'Content filtered by safety system', undefined, data);
143
+ default:
144
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.UNKNOWN, `Unexpected finish_reason: ${choice.finish_reason}`, undefined, data);
145
+ }
146
+ // Apply normalizeResponse if provided
147
+ const normalizedData = options?.normalizeResponse ? options.normalizeResponse(data) : data;
148
+ const normalizedChoice = normalizedData.choices?.[0];
149
+ // Get tool name from response
150
+ const toolCallName = normalizedChoice?.message?.tool_calls?.[0]?.function?.name;
151
+ if (!toolCallName) {
152
+ // When LLM returns no valid tool_call (finish_reason: 'stop', empty tool_calls, etc.),
153
+ // treat it as a graceful fallback rather than throwing an error.
154
+ const textContent = normalizedChoice?.message?.content;
155
+ // Try to find a suitable tool for synthetic call:
156
+ // 1. Prefer "done" or "AgentOutput" (main loop tools)
157
+ // 2. Fall back to the first available tool (sub-tool calls like VerifyResult)
158
+ const doneTool = tools['done'] || tools['AgentOutput'] || Object.values(tools)[0];
159
+ if (doneTool) {
160
+ // For "done"/"AgentOutput", wrap as { text, success }
161
+ // For other tools, try to parse textContent as JSON; fallback to minimal valid input
162
+ let syntheticArgs;
163
+ const toolName = doneTool === tools['done'] ? 'done'
164
+ : doneTool === tools['AgentOutput'] ? 'AgentOutput'
165
+ : Object.keys(tools)[0];
166
+ if (toolName === 'done' || toolName === 'AgentOutput') {
167
+ syntheticArgs = { text: textContent || '', success: true };
168
+ }
169
+ else {
170
+ // Try to extract JSON from the text content (LLM sometimes outputs JSON in text)
171
+ if (textContent) {
172
+ const jsonMatch = textContent.match(/\{[\s\S]*\}/);
173
+ if (jsonMatch) {
174
+ try {
175
+ syntheticArgs = JSON.parse(jsonMatch[0]);
176
+ }
177
+ catch {
178
+ syntheticArgs = {};
179
+ }
180
+ }
181
+ else {
182
+ syntheticArgs = {};
183
+ }
184
+ }
185
+ else {
186
+ syntheticArgs = {};
187
+ }
188
+ }
189
+ const validation = doneTool.inputSchema.safeParse(syntheticArgs);
190
+ const toolInput = validation.success ? validation.data : syntheticArgs;
191
+ let toolResult;
192
+ try {
193
+ toolResult = validation.success ? await doneTool.execute(toolInput) : toolInput;
194
+ }
195
+ catch {
196
+ toolResult = toolInput;
197
+ }
198
+ return {
199
+ toolCall: {
200
+ name: toolName,
201
+ args: toolInput,
202
+ },
203
+ toolResult,
204
+ usage: {
205
+ promptTokens: data.usage?.prompt_tokens ?? 0,
206
+ completionTokens: data.usage?.completion_tokens ?? 0,
207
+ totalTokens: data.usage?.total_tokens ?? 0,
208
+ cachedTokens: data.usage?.prompt_tokens_details?.cached_tokens,
209
+ reasoningTokens: data.usage?.completion_tokens_details?.reasoning_tokens,
210
+ },
211
+ rawResponse: data,
212
+ };
213
+ }
214
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.NO_TOOL_CALL, 'No tool call found in response', undefined, data);
215
+ }
216
+ const tool = tools[toolCallName];
217
+ if (!tool) {
218
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.UNKNOWN, `Tool "${toolCallName}" not found in tools`, undefined, data);
219
+ }
220
+ // Extract and parse tool arguments
221
+ const argString = normalizedChoice.message?.tool_calls?.[0]?.function?.arguments;
222
+ if (!argString) {
223
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.INVALID_TOOL_ARGS, 'No tool call arguments found', undefined, data);
224
+ }
225
+ let parsedArgs;
226
+ try {
227
+ parsedArgs = JSON.parse(argString);
228
+ }
229
+ catch (jsonError) {
230
+ // LLM sometimes generates slightly malformed JSON (e.g. trailing characters,
231
+ // mismatched quotes, or extra text after the closing brace).
232
+ // Attempt to recover by extracting the first valid JSON object from the string.
233
+ const recovered = this.tryRecoverJSON(argString);
234
+ if (recovered !== undefined) {
235
+ parsedArgs = recovered;
236
+ }
237
+ else {
238
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.INVALID_TOOL_ARGS, 'Failed to parse tool arguments as JSON', jsonError, data);
239
+ }
240
+ }
241
+ // Validate with schema
242
+ const validation = tool.inputSchema.safeParse(parsedArgs);
243
+ if (!validation.success) {
244
+ console.error(z.prettifyError(validation.error));
245
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.INVALID_TOOL_ARGS, 'Tool arguments validation failed', validation.error, data);
246
+ }
247
+ const toolInput = validation.data;
248
+ // 5. Execute tool
249
+ let toolResult;
250
+ try {
251
+ toolResult = await tool.execute(toolInput);
252
+ }
253
+ catch (e) {
254
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.TOOL_EXECUTION_ERROR, `Tool execution failed: ${e.message}`, e, data);
255
+ }
256
+ // Return result
257
+ return {
258
+ toolCall: {
259
+ name: toolCallName,
260
+ args: toolInput,
261
+ },
262
+ toolResult,
263
+ usage: {
264
+ promptTokens: data.usage?.prompt_tokens ?? 0,
265
+ completionTokens: data.usage?.completion_tokens ?? 0,
266
+ totalTokens: data.usage?.total_tokens ?? 0,
267
+ cachedTokens: data.usage?.prompt_tokens_details?.cached_tokens,
268
+ reasoningTokens: data.usage?.completion_tokens_details?.reasoning_tokens,
269
+ },
270
+ rawResponse: data,
271
+ rawRequest: requestBody,
272
+ };
273
+ }
274
+ /**
275
+ * Attempt to recover a valid JSON object from a malformed LLM response.
276
+ * Handles common LLM JSON errors:
277
+ * 1. Extra text after closing brace (e.g. "...}{extra")
278
+ * 2. Trailing commas inside objects
279
+ * 3. Unbalanced braces — find the deepest matching pair
280
+ * 4. Mixed quote styles (single quotes instead of double quotes)
281
+ */
282
+ tryRecoverJSON(argString) {
283
+ // Strategy 1: Find the first complete JSON object using brace matching
284
+ const firstBrace = argString.indexOf('{');
285
+ if (firstBrace === -1)
286
+ return undefined;
287
+ let depth = 0;
288
+ let inString = false;
289
+ let escapeNext = false;
290
+ for (let i = firstBrace; i < argString.length; i++) {
291
+ const ch = argString[i];
292
+ if (escapeNext) {
293
+ escapeNext = false;
294
+ continue;
295
+ }
296
+ if (ch === '\\') {
297
+ escapeNext = true;
298
+ continue;
299
+ }
300
+ if (ch === '"' && !escapeNext) {
301
+ inString = !inString;
302
+ continue;
303
+ }
304
+ if (inString)
305
+ continue;
306
+ if (ch === '{')
307
+ depth++;
308
+ if (ch === '}') {
309
+ depth--;
310
+ if (depth === 0) {
311
+ // Extract the matched JSON object
312
+ const candidate = argString.substring(firstBrace, i + 1);
313
+ try {
314
+ return JSON.parse(candidate);
315
+ }
316
+ catch {
317
+ // Strategy 2: Fix common issues
318
+ let fixed = candidate;
319
+ // Remove trailing commas before } or ]
320
+ fixed = fixed.replace(/,\s*([}\]])/g, '$1');
321
+ // Replace single-quoted property names with double quotes
322
+ fixed = fixed.replace(/'([^']+)'\s*:/g, '"$1":');
323
+ // Replace single-quoted string values with double quotes (careful with nested)
324
+ fixed = fixed.replace(/:\s*'([^']*)'/g, ': "$1"');
325
+ try {
326
+ return JSON.parse(fixed);
327
+ }
328
+ catch {
329
+ continue; // try deeper nesting
330
+ }
331
+ }
332
+ }
333
+ }
334
+ }
335
+ return undefined;
336
+ }
337
+ /**
338
+ * Call the LLM and return the full assistant message without executing any tools.
339
+ * Used by the agent loop to get the LLM's response and process tool_calls separately.
340
+ *
341
+ * This method:
342
+ * 1. Sends messages + tools to the OpenAI-compatible API
343
+ * 2. Returns the full assistant Message (with content + tool_calls)
344
+ * 3. Does NOT execute any tools or validate tool args
345
+ *
346
+ * The agent loop handles tool execution via its own ToolRegistry.
347
+ */
348
+ async chat(messages, tools, abortSignal, options) {
349
+ // Convert tools to OpenAI format (same as invoke)
350
+ const openaiTools = Object.entries(tools).map(([name, t]) => {
351
+ if (t.customJsonSchema) {
352
+ return {
353
+ type: 'function',
354
+ function: {
355
+ name,
356
+ description: t.description,
357
+ parameters: t.customJsonSchema,
358
+ },
359
+ };
360
+ }
361
+ return (0, utils_1.zodToOpenAITool)(name, t);
362
+ });
363
+ // Build request body
364
+ let toolChoice = 'required';
365
+ if (options?.toolChoiceName && !this.config.disableNamedToolChoice) {
366
+ toolChoice = { type: 'function', function: { name: options.toolChoiceName } };
367
+ }
368
+ else if (this.config.disableNamedToolChoice) {
369
+ toolChoice = undefined;
370
+ }
371
+ // For agent loop, we want the LLM to have freedom to respond
372
+ // with text or tool_calls. Use 'auto' instead of 'required'.
373
+ if (!options?.toolChoiceName) {
374
+ toolChoice = 'auto';
375
+ }
376
+ const requestBody = {
377
+ model: this.config.model,
378
+ temperature: this.config.temperature,
379
+ messages,
380
+ tools: openaiTools,
381
+ parallel_tool_calls: false,
382
+ ...(toolChoice !== undefined && { tool_choice: toolChoice }),
383
+ };
384
+ (0, utils_1.modelPatch)(requestBody);
385
+ // Call API
386
+ let response;
387
+ try {
388
+ response = await this.fetch(`${this.config.baseURL}/chat/completions`, {
389
+ method: 'POST',
390
+ headers: {
391
+ 'Content-Type': 'application/json',
392
+ ...(this.config.apiKey && { Authorization: `Bearer ${this.config.apiKey}` }),
393
+ },
394
+ body: JSON.stringify(requestBody),
395
+ signal: abortSignal,
396
+ });
397
+ }
398
+ catch (error) {
399
+ const isAbortError = error?.name === 'AbortError';
400
+ const errorMessage = isAbortError ? 'Network request aborted' : 'Network request failed';
401
+ if (!isAbortError)
402
+ console.error(error);
403
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.NETWORK_ERROR, errorMessage, error);
404
+ }
405
+ // Handle HTTP errors (same as invoke)
406
+ if (!response.ok) {
407
+ const errorData = await response.json().catch();
408
+ const errorMessage = errorData.error?.message || response.statusText;
409
+ if (response.status === 401 || response.status === 403) {
410
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.AUTH_ERROR, `Authentication failed: ${errorMessage}`, errorData);
411
+ }
412
+ if (response.status === 429) {
413
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.RATE_LIMIT, `Rate limit exceeded: ${errorMessage}`, errorData);
414
+ }
415
+ if (response.status >= 500) {
416
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.SERVER_ERROR, `Server error: ${errorMessage}`, errorData);
417
+ }
418
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.UNKNOWN, `HTTP ${response.status}: ${errorMessage}`, errorData);
419
+ }
420
+ // Parse response and extract full assistant message
421
+ const data = await response.json();
422
+ const choice = data.choices?.[0];
423
+ if (!choice) {
424
+ throw new errors_1.InvokeError(errors_1.InvokeErrorType.UNKNOWN, 'No choices in response', data);
425
+ }
426
+ // Build the assistant Message from the response
427
+ const assistantMessage = {
428
+ role: 'assistant',
429
+ content: choice.message?.content ?? null,
430
+ };
431
+ // Include tool_calls if present
432
+ if (choice.message?.tool_calls?.length) {
433
+ assistantMessage.tool_calls = choice.message.tool_calls.map((tc) => ({
434
+ id: tc.id,
435
+ type: 'function',
436
+ function: {
437
+ name: tc.function.name,
438
+ arguments: tc.function.arguments,
439
+ },
440
+ }));
441
+ }
442
+ return {
443
+ message: assistantMessage,
444
+ usage: {
445
+ promptTokens: data.usage?.prompt_tokens ?? 0,
446
+ completionTokens: data.usage?.completion_tokens ?? 0,
447
+ totalTokens: data.usage?.total_tokens ?? 0,
448
+ cachedTokens: data.usage?.prompt_tokens_details?.cached_tokens,
449
+ reasoningTokens: data.usage?.completion_tokens_details?.reasoning_tokens,
450
+ },
451
+ rawResponse: data,
452
+ };
453
+ }
454
+ }
455
+ exports.OpenAIClient = OpenAIClient;
@@ -0,0 +1,2 @@
1
+ export declare const LLM_MAX_RETRIES = 2;
2
+ export declare const DEFAULT_TEMPERATURE = 0.7;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_TEMPERATURE = exports.LLM_MAX_RETRIES = void 0;
4
+ // Internal constants
5
+ exports.LLM_MAX_RETRIES = 2;
6
+ exports.DEFAULT_TEMPERATURE = 0.7;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Error types and error handling for LLM invocations
3
+ */
4
+ export declare const InvokeErrorType: {
5
+ readonly NETWORK_ERROR: "network_error";
6
+ readonly RATE_LIMIT: "rate_limit";
7
+ readonly SERVER_ERROR: "server_error";
8
+ readonly NO_TOOL_CALL: "no_tool_call";
9
+ readonly INVALID_TOOL_ARGS: "invalid_tool_args";
10
+ readonly TOOL_EXECUTION_ERROR: "tool_execution_error";
11
+ readonly UNKNOWN: "unknown";
12
+ readonly AUTH_ERROR: "auth_error";
13
+ readonly CONTEXT_LENGTH: "context_length";
14
+ readonly CONTENT_FILTER: "content_filter";
15
+ };
16
+ export type InvokeErrorType = (typeof InvokeErrorType)[keyof typeof InvokeErrorType];
17
+ export declare class InvokeError extends Error {
18
+ type: InvokeErrorType;
19
+ retryable: boolean;
20
+ statusCode?: number;
21
+ rawError?: unknown;
22
+ rawResponse?: unknown;
23
+ constructor(type: InvokeErrorType, message: string, rawError?: unknown, rawResponse?: unknown);
24
+ private isRetryable;
25
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * Error types and error handling for LLM invocations
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.InvokeError = exports.InvokeErrorType = void 0;
7
+ exports.InvokeErrorType = {
8
+ // Retryable
9
+ NETWORK_ERROR: 'network_error',
10
+ RATE_LIMIT: 'rate_limit',
11
+ SERVER_ERROR: 'server_error',
12
+ NO_TOOL_CALL: 'no_tool_call',
13
+ INVALID_TOOL_ARGS: 'invalid_tool_args',
14
+ TOOL_EXECUTION_ERROR: 'tool_execution_error',
15
+ UNKNOWN: 'unknown',
16
+ // Non-retryable
17
+ AUTH_ERROR: 'auth_error',
18
+ CONTEXT_LENGTH: 'context_length',
19
+ CONTENT_FILTER: 'content_filter',
20
+ };
21
+ class InvokeError extends Error {
22
+ type;
23
+ retryable;
24
+ statusCode;
25
+ rawError;
26
+ rawResponse;
27
+ constructor(type, message, rawError, rawResponse) {
28
+ super(message);
29
+ this.name = 'InvokeError';
30
+ this.type = type;
31
+ this.retryable = this.isRetryable(type, rawError);
32
+ this.rawError = rawError;
33
+ this.rawResponse = rawResponse;
34
+ }
35
+ isRetryable(type, rawError) {
36
+ const isAbortError = rawError?.name === 'AbortError';
37
+ if (isAbortError)
38
+ return false;
39
+ const retryableTypes = [
40
+ exports.InvokeErrorType.NETWORK_ERROR,
41
+ exports.InvokeErrorType.RATE_LIMIT,
42
+ exports.InvokeErrorType.SERVER_ERROR,
43
+ exports.InvokeErrorType.NO_TOOL_CALL,
44
+ exports.InvokeErrorType.INVALID_TOOL_ARGS,
45
+ exports.InvokeErrorType.TOOL_EXECUTION_ERROR,
46
+ exports.InvokeErrorType.UNKNOWN,
47
+ ];
48
+ return retryableTypes.includes(type);
49
+ }
50
+ }
51
+ exports.InvokeError = InvokeError;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * LLM client with MacroTool support
3
+ *
4
+ * Key features:
5
+ * - Direct fetch to OpenAI API (no LangChain dependency)
6
+ * - Zod schema for tool definitions
7
+ * - toolChoiceName support (force specific tool call)
8
+ * - parallel_tool_calls: false (one tool per step)
9
+ * - Retry with error classification
10
+ * - Model-specific patches (Claude, Qwen, GPT, etc.)
11
+ */
12
+ import { InvokeError, InvokeErrorType } from './errors';
13
+ import type { ChatResult, ContentItem, InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types';
14
+ import { parseXMLExtractionResponse, extractXMLTag } from './utils';
15
+ export { InvokeError, InvokeErrorType };
16
+ export { parseXMLExtractionResponse, extractXMLTag };
17
+ export type { ChatResult, ContentItem, InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool };
18
+ export declare function parseLLMConfig(config: LLMConfig): Required<LLMConfig>;
19
+ export declare class LLM extends EventTarget {
20
+ config: Required<LLMConfig>;
21
+ client: LLMClient;
22
+ constructor(config: LLMConfig);
23
+ /**
24
+ * Call LLM API once, invoke tool call once, return the result.
25
+ * This is the core method used by gui-agent's MacroTool loop.
26
+ */
27
+ invoke(messages: Message[], tools: Record<string, Tool>, abortSignal?: AbortSignal, options?: InvokeOptions): Promise<InvokeResult>;
28
+ /**
29
+ * Simple chat call — no tools, just text generation.
30
+ * Used for validation and other non-tool-call LLM tasks.
31
+ */
32
+ chat(messages: Message[], abortSignal?: AbortSignal): Promise<{
33
+ content: string;
34
+ usage: {
35
+ prompt_tokens: number;
36
+ completion_tokens: number;
37
+ total_tokens: number;
38
+ };
39
+ }>;
40
+ /**
41
+ * Agent loop chat — call LLM with tools and return the full assistant message.
42
+ * Unlike invoke(), this does NOT execute tools. The agent loop handles tool
43
+ * execution via its own ToolRegistry after getting the response.
44
+ *
45
+ * Uses tool_choice: 'auto' by default (LLM can choose text or tool_calls).
46
+ * Includes retry logic with exponential backoff.
47
+ */
48
+ chatWithTools(messages: Message[], tools: Record<string, Tool>, abortSignal?: AbortSignal, options?: InvokeOptions): Promise<ChatResult>;
49
+ }