@page-agent/llms 0.0.20 → 0.0.22

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.
@@ -1,17 +1,21 @@
1
1
  import { z } from 'zod';
2
2
 
3
3
  /**
4
- * Agent brain state - the reflection-before-action model
5
- *
6
- * Every tool call must first reflect on:
7
- * - evaluation_previous_goal: How well did the previous action achieve its goal?
8
- * - memory: Key information to remember for future steps
9
- * - next_goal: What should be accomplished in the next action?
4
+ * Invoke options for LLM call
10
5
  */
11
- export declare interface AgentBrain {
12
- evaluation_previous_goal: string;
13
- memory: string;
14
- next_goal: string;
6
+ export declare interface InvokeOptions {
7
+ /**
8
+ * Force LLM to call a specific tool by name.
9
+ * If provided: tool_choice = { type: 'function', function: { name: toolChoiceName } }
10
+ * If not provided: tool_choice = 'required' (must call some tool, but model chooses which)
11
+ */
12
+ toolChoiceName?: string;
13
+ /**
14
+ * Response normalization function.
15
+ * Called before parsing the response.
16
+ * Used to fix various response format errors from the model.
17
+ */
18
+ normalizeResponse?: (response: any) => any;
15
19
  }
16
20
 
17
21
  /**
@@ -42,7 +46,7 @@ export declare class LLM extends EventTarget {
42
46
  * - invoke tool call *once*
43
47
  * - return the result of the tool
44
48
  */
45
- invoke(messages: Message[], tools: Record<string, Tool>, abortSignal: AbortSignal): Promise<InvokeResult>;
49
+ invoke(messages: Message[], tools: Record<string, Tool>, abortSignal: AbortSignal, options?: InvokeOptions): Promise<InvokeResult>;
46
50
  }
47
51
 
48
52
  /**
@@ -50,7 +54,7 @@ export declare class LLM extends EventTarget {
50
54
  * Note: Does not use generics because each tool in the tools array has different types
51
55
  */
52
56
  export declare interface LLMClient {
53
- invoke(messages: Message[], tools: Record<string, Tool>, abortSignal?: AbortSignal): Promise<InvokeResult>;
57
+ invoke(messages: Message[], tools: Record<string, Tool>, abortSignal?: AbortSignal, options?: InvokeOptions): Promise<InvokeResult>;
54
58
  }
55
59
 
56
60
  /**
@@ -70,24 +74,6 @@ export declare interface LLMConfig {
70
74
  customFetch?: typeof globalThis.fetch;
71
75
  }
72
76
 
73
- /**
74
- * MacroTool input structure
75
- *
76
- * This is the core abstraction that enforces the "reflection-before-action" mental model.
77
- * Before executing any action, the LLM must output its reasoning state.
78
- */
79
- export declare interface MacroToolInput extends AgentBrain {
80
- action: Record<string, any>;
81
- }
82
-
83
- /**
84
- * MacroTool output structure
85
- */
86
- export declare interface MacroToolResult {
87
- input: MacroToolInput;
88
- output: string;
89
- }
90
-
91
77
  /**
92
78
  * Message format - OpenAI standard (industry standard)
93
79
  */
@@ -67,135 +67,24 @@ function zodToOpenAITool(name, tool) {
67
67
  };
68
68
  }
69
69
  __name(zodToOpenAITool, "zodToOpenAITool");
70
- function lenientParseMacroToolCall(responseData, inputSchema) {
71
- const choice = responseData.choices?.[0];
72
- if (!choice) {
73
- throw new InvokeError(InvokeErrorType.UNKNOWN, "No choices in response", responseData);
74
- }
75
- switch (choice.finish_reason) {
76
- case "tool_calls":
77
- case "function_call":
78
- // gemini
79
- case "stop":
80
- break;
81
- case "length":
82
- throw new InvokeError(
83
- InvokeErrorType.CONTEXT_LENGTH,
84
- "Response truncated: max tokens reached"
85
- );
86
- case "content_filter":
87
- throw new InvokeError(InvokeErrorType.CONTENT_FILTER, "Content filtered by safety system");
88
- default:
89
- throw new InvokeError(
90
- InvokeErrorType.UNKNOWN,
91
- `Unexpected finish_reason: ${choice.finish_reason}`
92
- );
93
- }
94
- const actionSchema = inputSchema.shape.action;
95
- if (!actionSchema) {
96
- throw new Error('inputSchema must have an "action" field');
97
- }
98
- let arg = null;
99
- const toolCall = choice.message?.tool_calls?.[0]?.function;
100
- arg = toolCall?.arguments ?? null;
101
- if (arg && toolCall.name !== "AgentOutput") {
102
- console.log(chalk.yellow("lenientParseMacroToolCall: #1 fixing incorrect tool call"));
103
- let tmpArg;
104
- try {
105
- tmpArg = JSON.parse(arg);
106
- } catch (error) {
107
- throw new InvokeError(
108
- InvokeErrorType.INVALID_TOOL_ARGS,
109
- "Failed to parse tool arguments as JSON",
110
- error
111
- );
112
- }
113
- arg = JSON.stringify({ action: { [toolCall.name]: tmpArg } });
114
- }
115
- if (!arg) {
116
- arg = choice.message?.content.trim() || null;
117
- }
118
- if (!arg) {
119
- throw new InvokeError(
120
- InvokeErrorType.NO_TOOL_CALL,
121
- "No tool call or content found in response",
122
- responseData
123
- );
124
- }
125
- let parsedArgs;
126
- try {
127
- parsedArgs = JSON.parse(arg);
128
- } catch (error) {
129
- throw new InvokeError(
130
- InvokeErrorType.INVALID_TOOL_ARGS,
131
- "Failed to parse tool arguments as JSON",
132
- error
133
- );
134
- }
135
- if (parsedArgs.action || parsedArgs.evaluation_previous_goal || parsedArgs.next_goal) {
136
- if (!parsedArgs.action) {
137
- console.log(chalk.yellow("lenientParseMacroToolCall: #2 fixing incorrect tool call"));
138
- parsedArgs.action = {
139
- wait: { seconds: 1 }
140
- };
141
- }
142
- } else if (parsedArgs.type && parsedArgs.function) {
143
- if (parsedArgs.function.name !== "AgentOutput")
144
- throw new InvokeError(
145
- InvokeErrorType.INVALID_TOOL_ARGS,
146
- `Expected function name "AgentOutput", got "${parsedArgs.function.name}"`,
147
- null
148
- );
149
- console.log(chalk.yellow("lenientParseMacroToolCall: #3 fixing incorrect tool call"));
150
- parsedArgs = parsedArgs.function.arguments;
151
- } else if (parsedArgs.name && parsedArgs.arguments) {
152
- if (parsedArgs.name !== "AgentOutput")
153
- throw new InvokeError(
154
- InvokeErrorType.INVALID_TOOL_ARGS,
155
- `Expected function name "AgentOutput", got "${parsedArgs.name}"`,
156
- null
157
- );
158
- console.log(chalk.yellow("lenientParseMacroToolCall: #4 fixing incorrect tool call"));
159
- parsedArgs = parsedArgs.arguments;
160
- } else {
161
- console.log(chalk.yellow("lenientParseMacroToolCall: #5 fixing incorrect tool call"));
162
- parsedArgs = { action: parsedArgs };
163
- }
164
- if (typeof parsedArgs === "string") {
165
- console.log(chalk.yellow("lenientParseMacroToolCall: #6 fixing incorrect tool call"));
166
- try {
167
- parsedArgs = JSON.parse(parsedArgs);
168
- } catch (error) {
169
- throw new InvokeError(
170
- InvokeErrorType.INVALID_TOOL_ARGS,
171
- "Failed to parse nested tool arguments as JSON",
172
- error
173
- );
174
- }
175
- }
176
- const validation = inputSchema.safeParse(parsedArgs);
177
- if (validation.success) {
178
- return validation.data;
179
- } else {
180
- const action = parsedArgs.action ?? {};
181
- const actionName = Object.keys(action)[0] || "unknown";
182
- const actionArgs = JSON.stringify(action[actionName] || "unknown");
183
- throw new InvokeError(
184
- InvokeErrorType.INVALID_TOOL_ARGS,
185
- `Tool arguments validation failed: action "${actionName}" with args ${actionArgs}`,
186
- validation.error
187
- );
188
- }
189
- }
190
- __name(lenientParseMacroToolCall, "lenientParseMacroToolCall");
191
70
  function modelPatch(body) {
192
71
  const model = body.model || "";
193
72
  if (!model) return body;
194
73
  const modelName = normalizeModelName(model);
74
+ if (modelName.startsWith("qwen")) {
75
+ debug("Applying Qwen patch: use higher temperature for auto fixing");
76
+ body.temperature = Math.max(body.temperature || 0, 1);
77
+ }
195
78
  if (modelName.startsWith("claude")) {
196
- debug("Applying Claude patch: change tool_choice and disable thinking");
197
- body.tool_choice = { type: "tool", name: "AgentOutput" };
79
+ debug("Applying Claude patch: disable thinking");
198
80
  body.thinking = { type: "disabled" };
81
+ if (body.tool_choice === "required") {
82
+ debug('Applying Claude patch: convert tool_choice "required" to { type: "any" }');
83
+ body.tool_choice = { type: "any" };
84
+ } else if (body.tool_choice?.function?.name) {
85
+ debug("Applying Claude patch: convert tool_choice format");
86
+ body.tool_choice = { type: "tool", name: body.tool_choice.function.name };
87
+ }
199
88
  }
200
89
  if (modelName.startsWith("grok")) {
201
90
  debug("Applying Grok patch: removing tool_choice");
@@ -242,8 +131,17 @@ const _OpenAIClient = class _OpenAIClient {
242
131
  this.config = config;
243
132
  this.fetch = config.customFetch;
244
133
  }
245
- async invoke(messages, tools, abortSignal) {
246
- const openaiTools = Object.entries(tools).map(([name, tool2]) => zodToOpenAITool(name, tool2));
134
+ async invoke(messages, tools, abortSignal, options) {
135
+ const openaiTools = Object.entries(tools).map(([name, t]) => zodToOpenAITool(name, t));
136
+ const requestBody = {
137
+ model: this.config.model,
138
+ temperature: this.config.temperature,
139
+ messages,
140
+ tools: openaiTools,
141
+ parallel_tool_calls: false,
142
+ // Require tool call: specific tool if provided, otherwise any tool
143
+ tool_choice: options?.toolChoiceName ? { type: "function", function: { name: options.toolChoiceName } } : "required"
144
+ };
247
145
  let response;
248
146
  try {
249
147
  response = await this.fetch(`${this.config.baseURL}/chat/completions`, {
@@ -252,17 +150,7 @@ const _OpenAIClient = class _OpenAIClient {
252
150
  "Content-Type": "application/json",
253
151
  Authorization: `Bearer ${this.config.apiKey}`
254
152
  },
255
- body: JSON.stringify(
256
- modelPatch({
257
- model: this.config.model,
258
- temperature: this.config.temperature,
259
- messages,
260
- tools: openaiTools,
261
- // tool_choice: 'required',
262
- tool_choice: { type: "function", function: { name: "AgentOutput" } },
263
- parallel_tool_calls: false
264
- })
265
- ),
153
+ body: JSON.stringify(modelPatch(requestBody)),
266
154
  signal: abortSignal
267
155
  });
268
156
  } catch (error) {
@@ -300,11 +188,78 @@ const _OpenAIClient = class _OpenAIClient {
300
188
  );
301
189
  }
302
190
  const data = await response.json();
303
- const tool = tools.AgentOutput;
304
- const macroToolInput = lenientParseMacroToolCall(data, tool.inputSchema);
191
+ const choice = data.choices?.[0];
192
+ if (!choice) {
193
+ throw new InvokeError(InvokeErrorType.UNKNOWN, "No choices in response", data);
194
+ }
195
+ switch (choice.finish_reason) {
196
+ case "tool_calls":
197
+ case "function_call":
198
+ // gemini
199
+ case "stop":
200
+ break;
201
+ case "length":
202
+ throw new InvokeError(
203
+ InvokeErrorType.CONTEXT_LENGTH,
204
+ "Response truncated: max tokens reached"
205
+ );
206
+ case "content_filter":
207
+ throw new InvokeError(InvokeErrorType.CONTENT_FILTER, "Content filtered by safety system");
208
+ default:
209
+ throw new InvokeError(
210
+ InvokeErrorType.UNKNOWN,
211
+ `Unexpected finish_reason: ${choice.finish_reason}`
212
+ );
213
+ }
214
+ const normalizedData = options?.normalizeResponse ? options.normalizeResponse(data) : data;
215
+ const normalizedChoice = normalizedData.choices?.[0];
216
+ const toolCallName = normalizedChoice?.message?.tool_calls?.[0]?.function?.name;
217
+ if (!toolCallName) {
218
+ throw new InvokeError(
219
+ InvokeErrorType.NO_TOOL_CALL,
220
+ "No tool call found in response",
221
+ normalizedData
222
+ );
223
+ }
224
+ const tool = tools[toolCallName];
225
+ if (!tool) {
226
+ throw new InvokeError(
227
+ InvokeErrorType.UNKNOWN,
228
+ `Tool "${toolCallName}" not found in tools`,
229
+ normalizedData
230
+ );
231
+ }
232
+ const argString = normalizedChoice.message?.tool_calls?.[0]?.function?.arguments;
233
+ if (!argString) {
234
+ throw new InvokeError(
235
+ InvokeErrorType.INVALID_TOOL_ARGS,
236
+ "No tool call arguments found",
237
+ normalizedData
238
+ );
239
+ }
240
+ let parsedArgs;
241
+ try {
242
+ parsedArgs = JSON.parse(argString);
243
+ } catch (error) {
244
+ throw new InvokeError(
245
+ InvokeErrorType.INVALID_TOOL_ARGS,
246
+ "Failed to parse tool arguments as JSON",
247
+ error
248
+ );
249
+ }
250
+ const validation = tool.inputSchema.safeParse(parsedArgs);
251
+ if (!validation.success) {
252
+ console.error(validation.error);
253
+ throw new InvokeError(
254
+ InvokeErrorType.INVALID_TOOL_ARGS,
255
+ "Tool arguments validation failed",
256
+ validation.error
257
+ );
258
+ }
259
+ const toolInput = validation.data;
305
260
  let toolResult;
306
261
  try {
307
- toolResult = await tool.execute(macroToolInput);
262
+ toolResult = await tool.execute(toolInput);
308
263
  } catch (e) {
309
264
  throw new InvokeError(
310
265
  InvokeErrorType.TOOL_EXECUTION_ERROR,
@@ -314,9 +269,8 @@ const _OpenAIClient = class _OpenAIClient {
314
269
  }
315
270
  return {
316
271
  toolCall: {
317
- // id: toolCall.id,
318
- name: "AgentOutput",
319
- args: macroToolInput
272
+ name: toolCallName,
273
+ args: toolInput
320
274
  },
321
275
  toolResult,
322
276
  usage: {
@@ -362,10 +316,10 @@ const _LLM = class _LLM extends EventTarget {
362
316
  * - invoke tool call *once*
363
317
  * - return the result of the tool
364
318
  */
365
- async invoke(messages, tools, abortSignal) {
319
+ async invoke(messages, tools, abortSignal, options) {
366
320
  return await withRetry(
367
321
  async () => {
368
- const result = await this.client.invoke(messages, tools, abortSignal);
322
+ const result = await this.client.invoke(messages, tools, abortSignal, options);
369
323
  return result;
370
324
  },
371
325
  // retry settings
@@ -1 +1 @@
1
- {"version":3,"file":"page-agent-llms.js","sources":["../../src/errors.ts","../../src/utils.ts","../../src/OpenAILenientClient.ts","../../src/constants.ts","../../src/index.ts"],"sourcesContent":["/**\n * Error types and error handling for LLM invocations\n */\n\nexport const InvokeErrorType = {\n\t// Retryable\n\tNETWORK_ERROR: 'network_error', // Network error, retry\n\tRATE_LIMIT: 'rate_limit', // Rate limit, retry\n\tSERVER_ERROR: 'server_error', // 5xx, retry\n\tNO_TOOL_CALL: 'no_tool_call', // Model did not call tool\n\tINVALID_TOOL_ARGS: 'invalid_tool_args', // Tool args don't match schema\n\tTOOL_EXECUTION_ERROR: 'tool_execution_error', // Tool execution error\n\n\tUNKNOWN: 'unknown',\n\n\t// Non-retryable\n\tAUTH_ERROR: 'auth_error', // Authentication failed\n\tCONTEXT_LENGTH: 'context_length', // Prompt too long\n\tCONTENT_FILTER: 'content_filter', // Content filtered\n} as const\n\nexport type InvokeErrorType = (typeof InvokeErrorType)[keyof typeof InvokeErrorType]\n\nexport class InvokeError extends Error {\n\ttype: InvokeErrorType\n\tretryable: boolean\n\tstatusCode?: number\n\trawError?: unknown\n\n\tconstructor(type: InvokeErrorType, message: string, rawError?: unknown) {\n\t\tsuper(message)\n\t\tthis.name = 'InvokeError'\n\t\tthis.type = type\n\t\tthis.retryable = this.isRetryable(type)\n\t\tthis.rawError = rawError\n\t}\n\n\tprivate isRetryable(type: InvokeErrorType): boolean {\n\t\tconst retryableTypes: InvokeErrorType[] = [\n\t\t\tInvokeErrorType.NETWORK_ERROR,\n\t\t\tInvokeErrorType.RATE_LIMIT,\n\t\t\tInvokeErrorType.SERVER_ERROR,\n\t\t\tInvokeErrorType.NO_TOOL_CALL,\n\t\t\tInvokeErrorType.INVALID_TOOL_ARGS,\n\t\t\tInvokeErrorType.TOOL_EXECUTION_ERROR,\n\t\t\tInvokeErrorType.UNKNOWN,\n\t\t]\n\t\treturn retryableTypes.includes(type)\n\t}\n}\n","/**\n * Utility functions for LLM integration\n */\nimport chalk from 'chalk'\nimport { z } from 'zod'\n\nimport { InvokeError, InvokeErrorType } from './errors'\nimport type { MacroToolInput, Tool } from './types'\n\nfunction debug(message: string) {\n\tconsole.debug(chalk.gray('[LLM]'), message)\n}\n\n/**\n * Convert Zod schema to OpenAI tool format\n * Uses Zod 4 native z.toJSONSchema()\n */\nexport function zodToOpenAITool(name: string, tool: Tool) {\n\treturn {\n\t\ttype: 'function' as const,\n\t\tfunction: {\n\t\t\tname,\n\t\t\tdescription: tool.description,\n\t\t\tparameters: z.toJSONSchema(tool.inputSchema, { target: 'openapi-3.0' }),\n\t\t},\n\t}\n}\n\n/**\n * Although some models cannot guarantee correct response. Common issues are fixable:\n * - Instead of returning a proper tool call. Return the tool call parameters in the message content.\n * - Returned tool calls or messages don't follow the nested MacroToolInput format.\n */\nexport function lenientParseMacroToolCall(\n\tresponseData: any,\n\tinputSchema: z.ZodObject<MacroToolInput & Record<string, any>>\n): MacroToolInput {\n\t// check\n\tconst choice = responseData.choices?.[0]\n\tif (!choice) {\n\t\tthrow new InvokeError(InvokeErrorType.UNKNOWN, 'No choices in response', responseData)\n\t}\n\n\t// check\n\tswitch (choice.finish_reason) {\n\t\tcase 'tool_calls':\n\t\tcase 'function_call': // gemini\n\t\tcase 'stop': // will try a robust parse\n\t\t\t// ✅ Normal\n\t\t\tbreak\n\t\tcase 'length':\n\t\t\t// ⚠️ Token limit reached\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorType.CONTEXT_LENGTH,\n\t\t\t\t'Response truncated: max tokens reached'\n\t\t\t)\n\t\tcase 'content_filter':\n\t\t\t// ❌ Content filtered\n\t\t\tthrow new InvokeError(InvokeErrorType.CONTENT_FILTER, 'Content filtered by safety system')\n\t\tdefault:\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorType.UNKNOWN,\n\t\t\t\t`Unexpected finish_reason: ${choice.finish_reason}`\n\t\t\t)\n\t}\n\n\t// Extract action schema from MacroToolInput schema\n\tconst actionSchema = inputSchema.shape.action\n\tif (!actionSchema) {\n\t\tthrow new Error('inputSchema must have an \"action\" field')\n\t}\n\n\t// patch stopReason mis-format\n\n\tlet arg: string | null = null\n\n\t// try to use tool call\n\tconst toolCall = choice.message?.tool_calls?.[0]?.function\n\targ = toolCall?.arguments ?? null\n\n\tif (arg && toolCall.name !== 'AgentOutput') {\n\t\t// TODO: check if toolCall.name is a valid action name\n\t\t// case: instead of AgentOutput, the model returned a action name as tool call\n\t\tconsole.log(chalk.yellow('lenientParseMacroToolCall: #1 fixing incorrect tool call'))\n\t\tlet tmpArg\n\t\ttry {\n\t\t\ttmpArg = JSON.parse(arg)\n\t\t} catch (error) {\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorType.INVALID_TOOL_ARGS,\n\t\t\t\t'Failed to parse tool arguments as JSON',\n\t\t\t\terror\n\t\t\t)\n\t\t}\n\t\targ = JSON.stringify({ action: { [toolCall.name]: tmpArg } })\n\t}\n\n\tif (!arg) {\n\t\t// try to use message content as JSON\n\t\targ = choice.message?.content.trim() || null\n\t}\n\n\tif (!arg) {\n\t\tthrow new InvokeError(\n\t\t\tInvokeErrorType.NO_TOOL_CALL,\n\t\t\t'No tool call or content found in response',\n\t\t\tresponseData\n\t\t)\n\t}\n\n\t// make sure is valid JSON\n\n\tlet parsedArgs: any\n\ttry {\n\t\tparsedArgs = JSON.parse(arg)\n\t} catch (error) {\n\t\tthrow new InvokeError(\n\t\t\tInvokeErrorType.INVALID_TOOL_ARGS,\n\t\t\t'Failed to parse tool arguments as JSON',\n\t\t\terror\n\t\t)\n\t}\n\n\t// patch incomplete formats\n\n\tif (parsedArgs.action || parsedArgs.evaluation_previous_goal || parsedArgs.next_goal) {\n\t\t// case: nested MacroToolInput format (correct format)\n\n\t\t// some models may give a empty action (they may think reasoning and action should be separate)\n\t\tif (!parsedArgs.action) {\n\t\t\tconsole.log(chalk.yellow('lenientParseMacroToolCall: #2 fixing incorrect tool call'))\n\t\t\tparsedArgs.action = {\n\t\t\t\twait: { seconds: 1 },\n\t\t\t}\n\t\t}\n\t} else if (parsedArgs.type && parsedArgs.function) {\n\t\t// case: upper level function call format provided. only keep its arguments\n\t\t// TODO: check if function name is a valid action name\n\t\tif (parsedArgs.function.name !== 'AgentOutput')\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorType.INVALID_TOOL_ARGS,\n\t\t\t\t`Expected function name \"AgentOutput\", got \"${parsedArgs.function.name}\"`,\n\t\t\t\tnull\n\t\t\t)\n\n\t\tconsole.log(chalk.yellow('lenientParseMacroToolCall: #3 fixing incorrect tool call'))\n\t\tparsedArgs = parsedArgs.function.arguments\n\t} else if (parsedArgs.name && parsedArgs.arguments) {\n\t\t// case: upper level function call format provided. only keep its arguments\n\t\t// TODO: check if function name is a valid action name\n\t\tif (parsedArgs.name !== 'AgentOutput')\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorType.INVALID_TOOL_ARGS,\n\t\t\t\t`Expected function name \"AgentOutput\", got \"${parsedArgs.name}\"`,\n\t\t\t\tnull\n\t\t\t)\n\n\t\tconsole.log(chalk.yellow('lenientParseMacroToolCall: #4 fixing incorrect tool call'))\n\t\tparsedArgs = parsedArgs.arguments\n\t} else {\n\t\t// case: only action parameters provided, wrap into MacroToolInput\n\t\t// TODO: check if action name is valid\n\t\tconsole.log(chalk.yellow('lenientParseMacroToolCall: #5 fixing incorrect tool call'))\n\t\tparsedArgs = { action: parsedArgs } as MacroToolInput\n\t}\n\n\t// make sure it's not wrapped as string\n\tif (typeof parsedArgs === 'string') {\n\t\tconsole.log(chalk.yellow('lenientParseMacroToolCall: #6 fixing incorrect tool call'))\n\t\ttry {\n\t\t\tparsedArgs = JSON.parse(parsedArgs)\n\t\t} catch (error) {\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorType.INVALID_TOOL_ARGS,\n\t\t\t\t'Failed to parse nested tool arguments as JSON',\n\t\t\t\terror\n\t\t\t)\n\t\t}\n\t}\n\n\tconst validation = inputSchema.safeParse(parsedArgs)\n\tif (validation.success) {\n\t\treturn validation.data as unknown as MacroToolInput\n\t} else {\n\t\tconst action = parsedArgs.action ?? {}\n\t\tconst actionName = Object.keys(action)[0] || 'unknown'\n\t\tconst actionArgs = JSON.stringify(action[actionName] || 'unknown')\n\n\t\t// TODO: check if action name is valid. give a readable error message\n\n\t\tthrow new InvokeError(\n\t\t\tInvokeErrorType.INVALID_TOOL_ARGS,\n\t\t\t`Tool arguments validation failed: action \"${actionName}\" with args ${actionArgs}`,\n\t\t\tvalidation.error\n\t\t)\n\t}\n}\n\n/**\n * Patch model specific parameters\n */\nexport function modelPatch(body: Record<string, any>) {\n\tconst model: string = body.model || ''\n\tif (!model) return body\n\n\tconst modelName = normalizeModelName(model)\n\n\tif (modelName.startsWith('claude')) {\n\t\tdebug('Applying Claude patch: change tool_choice and disable thinking')\n\t\tbody.tool_choice = { type: 'tool', name: 'AgentOutput' }\n\t\tbody.thinking = { type: 'disabled' }\n\t\t// body.reasoning = { enabled: 'disabled' }\n\t}\n\n\tif (modelName.startsWith('grok')) {\n\t\tdebug('Applying Grok patch: removing tool_choice')\n\t\tdelete body.tool_choice\n\t\tdebug('Applying Grok patch: disable reasoning and thinking')\n\t\tbody.thinking = { type: 'disabled', effort: 'minimal' }\n\t\tbody.reasoning = { enabled: false, effort: 'low' }\n\t}\n\n\tif (modelName.startsWith('gpt')) {\n\t\tdebug('Applying GPT patch: set verbosity to low')\n\t\tbody.verbosity = 'low'\n\n\t\tif (modelName.startsWith('gpt-52')) {\n\t\t\tdebug('Applying GPT-52 patch: disable reasoning')\n\t\t\tbody.reasoning_effort = 'none'\n\t\t} else if (modelName.startsWith('gpt-51')) {\n\t\t\tdebug('Applying GPT-51 patch: disable reasoning')\n\t\t\tbody.reasoning_effort = 'none'\n\t\t} else if (modelName.startsWith('gpt-5')) {\n\t\t\tdebug('Applying GPT-5 patch: set reasoning effort to low')\n\t\t\tbody.reasoning_effort = 'low'\n\t\t}\n\t}\n\n\tif (modelName.startsWith('gemini')) {\n\t\tdebug('Applying Gemini patch: set reasoning effort to minimal')\n\t\tbody.reasoning_effort = 'minimal'\n\t}\n\n\treturn body\n}\n\n/**\n * check if a given model ID fits a specific model name\n *\n * @note\n * Different model providers may use different model IDs for the same model.\n * For example, openai's `gpt-5.2` may called:\n *\n * - `gpt-5.2-version`\n * - `gpt-5_2-date`\n * - `GPT-52-version-date`\n * - `openai/gpt-5.2-chat`\n *\n * They should be treated as the same model.\n * Normalize them to `gpt-52`\n */\nfunction normalizeModelName(modelName: string): string {\n\tlet normalizedName = modelName.toLowerCase()\n\n\t// remove prefix before '/'\n\tif (normalizedName.includes('/')) {\n\t\tnormalizedName = normalizedName.split('/')[1]\n\t}\n\n\t// remove '_'\n\tnormalizedName = normalizedName.replace(/_/g, '')\n\n\t// remove '.'\n\tnormalizedName = normalizedName.replace(/\\./g, '')\n\n\treturn normalizedName\n}\n","/**\n * OpenAI Client implementation\n */\nimport { InvokeError, InvokeErrorType } from './errors'\nimport type { InvokeResult, LLMClient, LLMConfig, MacroToolInput, Message, Tool } from './types'\nimport { lenientParseMacroToolCall, modelPatch, zodToOpenAITool } from './utils'\n\nexport class OpenAIClient implements LLMClient {\n\tconfig: Required<LLMConfig>\n\tprivate fetch: typeof globalThis.fetch\n\n\tconstructor(config: Required<LLMConfig>) {\n\t\tthis.config = config\n\t\tthis.fetch = config.customFetch\n\t}\n\n\tasync invoke(\n\t\tmessages: Message[],\n\t\ttools: { AgentOutput: Tool<MacroToolInput> },\n\t\tabortSignal?: AbortSignal\n\t): Promise<InvokeResult> {\n\t\t// 1. Convert tools to OpenAI format\n\t\tconst openaiTools = Object.entries(tools).map(([name, tool]) => zodToOpenAITool(name, tool))\n\n\t\t// 2. Call API\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await this.fetch(`${this.config.baseURL}/chat/completions`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\tAuthorization: `Bearer ${this.config.apiKey}`,\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(\n\t\t\t\t\tmodelPatch({\n\t\t\t\t\t\tmodel: this.config.model,\n\t\t\t\t\t\ttemperature: this.config.temperature,\n\t\t\t\t\t\tmessages,\n\n\t\t\t\t\t\ttools: openaiTools,\n\t\t\t\t\t\t// tool_choice: 'required',\n\t\t\t\t\t\ttool_choice: { type: 'function', function: { name: 'AgentOutput' } },\n\t\t\t\t\t\tparallel_tool_calls: false,\n\t\t\t\t\t})\n\t\t\t\t),\n\t\t\t\tsignal: abortSignal,\n\t\t\t})\n\t\t} catch (error: unknown) {\n\t\t\t// Network error\n\t\t\tconsole.error(error)\n\t\t\tthrow new InvokeError(InvokeErrorType.NETWORK_ERROR, 'Network request failed', error)\n\t\t}\n\n\t\t// 3. Handle HTTP errors\n\t\tif (!response.ok) {\n\t\t\tconst errorData = await response.json().catch()\n\t\t\tconst errorMessage =\n\t\t\t\t(errorData as { error?: { message?: string } }).error?.message || response.statusText\n\n\t\t\tif (response.status === 401 || response.status === 403) {\n\t\t\t\tthrow new InvokeError(\n\t\t\t\t\tInvokeErrorType.AUTH_ERROR,\n\t\t\t\t\t`Authentication failed: ${errorMessage}`,\n\t\t\t\t\terrorData\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (response.status === 429) {\n\t\t\t\tthrow new InvokeError(\n\t\t\t\t\tInvokeErrorType.RATE_LIMIT,\n\t\t\t\t\t`Rate limit exceeded: ${errorMessage}`,\n\t\t\t\t\terrorData\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (response.status >= 500) {\n\t\t\t\tthrow new InvokeError(\n\t\t\t\t\tInvokeErrorType.SERVER_ERROR,\n\t\t\t\t\t`Server error: ${errorMessage}`,\n\t\t\t\t\terrorData\n\t\t\t\t)\n\t\t\t}\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorType.UNKNOWN,\n\t\t\t\t`HTTP ${response.status}: ${errorMessage}`,\n\t\t\t\terrorData\n\t\t\t)\n\t\t}\n\n\t\t// parse response\n\n\t\tconst data = await response.json()\n\t\tconst tool = tools.AgentOutput\n\t\tconst macroToolInput = lenientParseMacroToolCall(data, tool.inputSchema as any)\n\n\t\t// Execute tool\n\t\tlet toolResult: unknown\n\t\ttry {\n\t\t\ttoolResult = await tool.execute(macroToolInput)\n\t\t} catch (e) {\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorType.TOOL_EXECUTION_ERROR,\n\t\t\t\t`Tool execution failed: ${(e as Error).message}`,\n\t\t\t\te\n\t\t\t)\n\t\t}\n\n\t\t// Return result (including cache tokens)\n\t\treturn {\n\t\t\ttoolCall: {\n\t\t\t\t// id: toolCall.id,\n\t\t\t\tname: 'AgentOutput',\n\t\t\t\targs: macroToolInput,\n\t\t\t},\n\t\t\ttoolResult,\n\t\t\tusage: {\n\t\t\t\tpromptTokens: data.usage?.prompt_tokens ?? 0,\n\t\t\t\tcompletionTokens: data.usage?.completion_tokens ?? 0,\n\t\t\t\ttotalTokens: data.usage?.total_tokens ?? 0,\n\t\t\t\tcachedTokens: data.usage?.prompt_tokens_details?.cached_tokens,\n\t\t\t\treasoningTokens: data.usage?.completion_tokens_details?.reasoning_tokens,\n\t\t\t},\n\t\t\trawResponse: data,\n\t\t}\n\t}\n}\n","// Dev environment: use .env config if available, otherwise fallback to testing api\nexport const DEFAULT_MODEL_NAME: string =\n\timport.meta.env.DEV && import.meta.env.LLM_MODEL_NAME\n\t\t? import.meta.env.LLM_MODEL_NAME\n\t\t: 'PAGE-AGENT-FREE-TESTING-RANDOM'\n\nexport const DEFAULT_API_KEY: string =\n\timport.meta.env.DEV && import.meta.env.LLM_API_KEY\n\t\t? import.meta.env.LLM_API_KEY\n\t\t: 'PAGE-AGENT-FREE-TESTING-RANDOM'\n\nexport const DEFAULT_BASE_URL: string =\n\timport.meta.env.DEV && import.meta.env.LLM_BASE_URL\n\t\t? import.meta.env.LLM_BASE_URL\n\t\t: 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'\n\n// internal\n\nexport const LLM_MAX_RETRIES = 2\nexport const DEFAULT_TEMPERATURE = 0.7 // higher randomness helps auto-recovery\n","/**\n * @topic LLM 与主流程的隔离\n * @reasoning\n * 将 llm 的调用和主流程分开是复杂的,\n * 因为 agent 的 tool call 通常集成在 llm 模块中,而而先得到 llm 返回,然后处理工具调用\n * tools 和 llm 调用的逻辑不可避免地耦合在一起,tool 的执行又和主流程耦合在一起\n * 而 history 的维护和更新逻辑,又必须嵌入多轮 tool call 中\n * @reasoning\n * - 放弃框架提供的自动的多轮调用,每轮调用都由主流程发起\n * - 理想情况下,llm 调用应该获得 structured output,然后由额外的模块触发 tool call,目前模型和框架都无法实现\n * - 当前只能将 llm api 和 本地 tool call 耦合在一起,不关心其中的衔接方式\n * @conclusion\n * - @llm responsibility boundary:\n * - call llm api with given messages and tools\n * - invoke tool call and get the result of the tool\n * - return the result to main loop\n * - @main_loop responsibility boundary:\n * - maintain all behaviors of an **agent**\n * @conclusion\n * - 这里的 llm 模块不是 agent,只负责一轮 llm 调用和工具调用,无状态\n */\n/**\n * @topic 结构化输出\n * @facts\n * - 几乎所有模型都支持 tool call schema\n * - 几乎所有模型都支持返回 json\n * - 只有 openAI/grok/gemini 支持 schema 并保证格式\n * - 主流模型都支持 tool_choice: required\n * - 除了 qwen 必须指定一个函数名 (9月上新后支持)\n * @conclusion\n * - 永远使用 tool call 来返回结构化数据,禁止模型直接返回(视为出错)\n * - 不能假设 tool 参数合法,必须有修复机制,而且修复也应该使用 tool call 返回\n */\nimport { OpenAIClient } from './OpenAILenientClient'\nimport {\n\tDEFAULT_API_KEY,\n\tDEFAULT_BASE_URL,\n\tDEFAULT_MODEL_NAME,\n\tDEFAULT_TEMPERATURE,\n\tLLM_MAX_RETRIES,\n} from './constants'\nimport { InvokeError } from './errors'\nimport type {\n\tAgentBrain,\n\tInvokeResult,\n\tLLMClient,\n\tLLMConfig,\n\tMacroToolInput,\n\tMacroToolResult,\n\tMessage,\n\tTool,\n} from './types'\n\nexport type {\n\tAgentBrain,\n\tInvokeResult,\n\tLLMClient,\n\tLLMConfig,\n\tMacroToolInput,\n\tMacroToolResult,\n\tMessage,\n\tTool,\n}\n\nexport function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {\n\treturn {\n\t\tbaseURL: config.baseURL ?? DEFAULT_BASE_URL,\n\t\tapiKey: config.apiKey ?? DEFAULT_API_KEY,\n\t\tmodel: config.model ?? DEFAULT_MODEL_NAME,\n\t\ttemperature: config.temperature ?? DEFAULT_TEMPERATURE,\n\t\tmaxRetries: config.maxRetries ?? LLM_MAX_RETRIES,\n\t\tcustomFetch: (config.customFetch ?? fetch).bind(globalThis), // fetch will be illegal unless bound\n\t}\n}\n\nexport class LLM extends EventTarget {\n\tconfig: Required<LLMConfig>\n\tclient: LLMClient\n\n\tconstructor(config: LLMConfig) {\n\t\tsuper()\n\t\tthis.config = parseLLMConfig(config)\n\n\t\t// Default to OpenAI client\n\t\tthis.client = new OpenAIClient(this.config)\n\t}\n\n\t/**\n\t * - call llm api *once*\n\t * - invoke tool call *once*\n\t * - return the result of the tool\n\t */\n\tasync invoke(\n\t\tmessages: Message[],\n\t\ttools: Record<string, Tool>,\n\t\tabortSignal: AbortSignal\n\t): Promise<InvokeResult> {\n\t\treturn await withRetry(\n\t\t\tasync () => {\n\t\t\t\tconst result = await this.client.invoke(messages, tools, abortSignal)\n\n\t\t\t\treturn result\n\t\t\t},\n\t\t\t// retry settings\n\t\t\t{\n\t\t\t\tmaxRetries: this.config.maxRetries,\n\t\t\t\tonRetry: (current: number) => {\n\t\t\t\t\tthis.dispatchEvent(\n\t\t\t\t\t\tnew CustomEvent('retry', { detail: { current, max: this.config.maxRetries } })\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t\tonError: (error: Error) => {\n\t\t\t\t\tthis.dispatchEvent(new CustomEvent('error', { detail: { error } }))\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\t}\n}\n\nasync function withRetry<T>(\n\tfn: () => Promise<T>,\n\tsettings: {\n\t\tmaxRetries: number\n\t\tonRetry: (retries: number) => void\n\t\tonError: (error: Error) => void\n\t}\n): Promise<T> {\n\tlet retries = 0\n\tlet lastError: Error | null = null\n\twhile (retries <= settings.maxRetries) {\n\t\tif (retries > 0) {\n\t\t\tsettings.onRetry(retries)\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 100))\n\t\t}\n\n\t\ttry {\n\t\t\treturn await fn()\n\t\t} catch (error: unknown) {\n\t\t\tconsole.error(error)\n\t\t\tsettings.onError(error as Error)\n\n\t\t\t// do not retry if aborted by user\n\t\t\tif ((error as { name?: string })?.name === 'AbortError') throw error\n\n\t\t\t// do not retry if error is not retryable (InvokeError)\n\t\t\tif (error instanceof InvokeError && !error.retryable) throw error\n\n\t\t\tlastError = error as Error\n\t\t\tretries++\n\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 100))\n\t\t}\n\t}\n\n\tthrow lastError!\n}\n"],"names":["tool"],"mappings":"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"}
1
+ {"version":3,"file":"page-agent-llms.js","sources":["../../src/errors.ts","../../src/utils.ts","../../src/OpenAIClient.ts","../../src/constants.ts","../../src/index.ts"],"sourcesContent":["/**\n * Error types and error handling for LLM invocations\n */\n\nexport const InvokeErrorType = {\n\t// Retryable\n\tNETWORK_ERROR: 'network_error', // Network error, retry\n\tRATE_LIMIT: 'rate_limit', // Rate limit, retry\n\tSERVER_ERROR: 'server_error', // 5xx, retry\n\tNO_TOOL_CALL: 'no_tool_call', // Model did not call tool\n\tINVALID_TOOL_ARGS: 'invalid_tool_args', // Tool args don't match schema\n\tTOOL_EXECUTION_ERROR: 'tool_execution_error', // Tool execution error\n\n\tUNKNOWN: 'unknown',\n\n\t// Non-retryable\n\tAUTH_ERROR: 'auth_error', // Authentication failed\n\tCONTEXT_LENGTH: 'context_length', // Prompt too long\n\tCONTENT_FILTER: 'content_filter', // Content filtered\n} as const\n\nexport type InvokeErrorType = (typeof InvokeErrorType)[keyof typeof InvokeErrorType]\n\nexport class InvokeError extends Error {\n\ttype: InvokeErrorType\n\tretryable: boolean\n\tstatusCode?: number\n\trawError?: unknown\n\n\tconstructor(type: InvokeErrorType, message: string, rawError?: unknown) {\n\t\tsuper(message)\n\t\tthis.name = 'InvokeError'\n\t\tthis.type = type\n\t\tthis.retryable = this.isRetryable(type)\n\t\tthis.rawError = rawError\n\t}\n\n\tprivate isRetryable(type: InvokeErrorType): boolean {\n\t\tconst retryableTypes: InvokeErrorType[] = [\n\t\t\tInvokeErrorType.NETWORK_ERROR,\n\t\t\tInvokeErrorType.RATE_LIMIT,\n\t\t\tInvokeErrorType.SERVER_ERROR,\n\t\t\tInvokeErrorType.NO_TOOL_CALL,\n\t\t\tInvokeErrorType.INVALID_TOOL_ARGS,\n\t\t\tInvokeErrorType.TOOL_EXECUTION_ERROR,\n\t\t\tInvokeErrorType.UNKNOWN,\n\t\t]\n\t\treturn retryableTypes.includes(type)\n\t}\n}\n","/**\n * Utility functions for LLM integration\n */\nimport chalk from 'chalk'\nimport { z } from 'zod'\n\nimport type { Tool } from './types'\n\nfunction debug(message: string) {\n\tconsole.debug(chalk.gray('[LLM]'), message)\n}\n\n/**\n * Convert Zod schema to OpenAI tool format\n * Uses Zod 4 native z.toJSONSchema()\n */\nexport function zodToOpenAITool(name: string, tool: Tool) {\n\treturn {\n\t\ttype: 'function' as const,\n\t\tfunction: {\n\t\t\tname,\n\t\t\tdescription: tool.description,\n\t\t\tparameters: z.toJSONSchema(tool.inputSchema, { target: 'openapi-3.0' }),\n\t\t},\n\t}\n}\n\n/**\n * Patch model specific parameters\n */\nexport function modelPatch(body: Record<string, any>) {\n\tconst model: string = body.model || ''\n\tif (!model) return body\n\n\tconst modelName = normalizeModelName(model)\n\n\tif (modelName.startsWith('qwen')) {\n\t\tdebug('Applying Qwen patch: use higher temperature for auto fixing')\n\t\tbody.temperature = Math.max(body.temperature || 0, 1.0)\n\t}\n\n\tif (modelName.startsWith('claude')) {\n\t\tdebug('Applying Claude patch: disable thinking')\n\t\tbody.thinking = { type: 'disabled' }\n\n\t\t// Convert tool_choice to Claude format\n\t\tif (body.tool_choice === 'required') {\n\t\t\t// 'required' -> { type: 'any' } (must call some tool)\n\t\t\tdebug('Applying Claude patch: convert tool_choice \"required\" to { type: \"any\" }')\n\t\t\tbody.tool_choice = { type: 'any' }\n\t\t} else if (body.tool_choice?.function?.name) {\n\t\t\t// { type: 'function', function: { name: '...' } } -> { type: 'tool', name: '...' }\n\t\t\tdebug('Applying Claude patch: convert tool_choice format')\n\t\t\tbody.tool_choice = { type: 'tool', name: body.tool_choice.function.name }\n\t\t}\n\t}\n\n\tif (modelName.startsWith('grok')) {\n\t\tdebug('Applying Grok patch: removing tool_choice')\n\t\tdelete body.tool_choice\n\t\tdebug('Applying Grok patch: disable reasoning and thinking')\n\t\tbody.thinking = { type: 'disabled', effort: 'minimal' }\n\t\tbody.reasoning = { enabled: false, effort: 'low' }\n\t}\n\n\tif (modelName.startsWith('gpt')) {\n\t\tdebug('Applying GPT patch: set verbosity to low')\n\t\tbody.verbosity = 'low'\n\n\t\tif (modelName.startsWith('gpt-52')) {\n\t\t\tdebug('Applying GPT-52 patch: disable reasoning')\n\t\t\tbody.reasoning_effort = 'none'\n\t\t} else if (modelName.startsWith('gpt-51')) {\n\t\t\tdebug('Applying GPT-51 patch: disable reasoning')\n\t\t\tbody.reasoning_effort = 'none'\n\t\t} else if (modelName.startsWith('gpt-5')) {\n\t\t\tdebug('Applying GPT-5 patch: set reasoning effort to low')\n\t\t\tbody.reasoning_effort = 'low'\n\t\t}\n\t}\n\n\tif (modelName.startsWith('gemini')) {\n\t\tdebug('Applying Gemini patch: set reasoning effort to minimal')\n\t\tbody.reasoning_effort = 'minimal'\n\t}\n\n\treturn body\n}\n\n/**\n * check if a given model ID fits a specific model name\n *\n * @note\n * Different model providers may use different model IDs for the same model.\n * For example, openai's `gpt-5.2` may called:\n *\n * - `gpt-5.2-version`\n * - `gpt-5_2-date`\n * - `GPT-52-version-date`\n * - `openai/gpt-5.2-chat`\n *\n * They should be treated as the same model.\n * Normalize them to `gpt-52`\n */\nfunction normalizeModelName(modelName: string): string {\n\tlet normalizedName = modelName.toLowerCase()\n\n\t// remove prefix before '/'\n\tif (normalizedName.includes('/')) {\n\t\tnormalizedName = normalizedName.split('/')[1]\n\t}\n\n\t// remove '_'\n\tnormalizedName = normalizedName.replace(/_/g, '')\n\n\t// remove '.'\n\tnormalizedName = normalizedName.replace(/\\./g, '')\n\n\treturn normalizedName\n}\n","/**\n * OpenAI Client implementation\n */\nimport { InvokeError, InvokeErrorType } from './errors'\nimport type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types'\nimport { modelPatch, zodToOpenAITool } from './utils'\n\n/**\n * Client for OpenAI compatible APIs\n */\nexport class OpenAIClient implements LLMClient {\n\tconfig: Required<LLMConfig>\n\tprivate fetch: typeof globalThis.fetch\n\n\tconstructor(config: Required<LLMConfig>) {\n\t\tthis.config = config\n\t\tthis.fetch = config.customFetch\n\t}\n\n\tasync invoke(\n\t\tmessages: Message[],\n\t\ttools: Record<string, Tool>,\n\t\tabortSignal?: AbortSignal,\n\t\toptions?: InvokeOptions\n\t): Promise<InvokeResult> {\n\t\t// 1. Convert tools to OpenAI format\n\t\tconst openaiTools = Object.entries(tools).map(([name, t]) => zodToOpenAITool(name, t))\n\n\t\t// Build request body\n\t\tconst requestBody: Record<string, unknown> = {\n\t\t\tmodel: this.config.model,\n\t\t\ttemperature: this.config.temperature,\n\t\t\tmessages,\n\t\t\ttools: openaiTools,\n\t\t\tparallel_tool_calls: false,\n\t\t\t// Require tool call: specific tool if provided, otherwise any tool\n\t\t\ttool_choice: options?.toolChoiceName\n\t\t\t\t? { type: 'function', function: { name: options.toolChoiceName } }\n\t\t\t\t: 'required',\n\t\t}\n\n\t\t// 2. Call API\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await this.fetch(`${this.config.baseURL}/chat/completions`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\tAuthorization: `Bearer ${this.config.apiKey}`,\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify(modelPatch(requestBody)),\n\t\t\t\tsignal: abortSignal,\n\t\t\t})\n\t\t} catch (error: unknown) {\n\t\t\tconsole.error(error)\n\t\t\tthrow new InvokeError(InvokeErrorType.NETWORK_ERROR, 'Network request failed', error)\n\t\t}\n\n\t\t// 3. Handle HTTP errors\n\t\tif (!response.ok) {\n\t\t\tconst errorData = await response.json().catch()\n\t\t\tconst errorMessage =\n\t\t\t\t(errorData as { error?: { message?: string } }).error?.message || response.statusText\n\n\t\t\tif (response.status === 401 || response.status === 403) {\n\t\t\t\tthrow new InvokeError(\n\t\t\t\t\tInvokeErrorType.AUTH_ERROR,\n\t\t\t\t\t`Authentication failed: ${errorMessage}`,\n\t\t\t\t\terrorData\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (response.status === 429) {\n\t\t\t\tthrow new InvokeError(\n\t\t\t\t\tInvokeErrorType.RATE_LIMIT,\n\t\t\t\t\t`Rate limit exceeded: ${errorMessage}`,\n\t\t\t\t\terrorData\n\t\t\t\t)\n\t\t\t}\n\t\t\tif (response.status >= 500) {\n\t\t\t\tthrow new InvokeError(\n\t\t\t\t\tInvokeErrorType.SERVER_ERROR,\n\t\t\t\t\t`Server error: ${errorMessage}`,\n\t\t\t\t\terrorData\n\t\t\t\t)\n\t\t\t}\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorType.UNKNOWN,\n\t\t\t\t`HTTP ${response.status}: ${errorMessage}`,\n\t\t\t\terrorData\n\t\t\t)\n\t\t}\n\n\t\t// 4. Parse and validate response\n\t\tconst data = await response.json()\n\n\t\tconst choice = data.choices?.[0]\n\t\tif (!choice) {\n\t\t\tthrow new InvokeError(InvokeErrorType.UNKNOWN, 'No choices in response', data)\n\t\t}\n\n\t\t// Check finish_reason\n\t\tswitch (choice.finish_reason) {\n\t\t\tcase 'tool_calls':\n\t\t\tcase 'function_call': // gemini\n\t\t\tcase 'stop': // some models use this even with tool calls\n\t\t\t\tbreak\n\t\t\tcase 'length':\n\t\t\t\tthrow new InvokeError(\n\t\t\t\t\tInvokeErrorType.CONTEXT_LENGTH,\n\t\t\t\t\t'Response truncated: max tokens reached'\n\t\t\t\t)\n\t\t\tcase 'content_filter':\n\t\t\t\tthrow new InvokeError(InvokeErrorType.CONTENT_FILTER, 'Content filtered by safety system')\n\t\t\tdefault:\n\t\t\t\tthrow new InvokeError(\n\t\t\t\t\tInvokeErrorType.UNKNOWN,\n\t\t\t\t\t`Unexpected finish_reason: ${choice.finish_reason}`\n\t\t\t\t)\n\t\t}\n\n\t\t// Apply normalizeResponse if provided (for fixing format issues automatically)\n\t\tconst normalizedData = options?.normalizeResponse ? options.normalizeResponse(data) : data\n\t\tconst normalizedChoice = (normalizedData as any).choices?.[0]\n\n\t\t// Get tool name from response\n\t\tconst toolCallName = normalizedChoice?.message?.tool_calls?.[0]?.function?.name\n\t\tif (!toolCallName) {\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorType.NO_TOOL_CALL,\n\t\t\t\t'No tool call found in response',\n\t\t\t\tnormalizedData\n\t\t\t)\n\t\t}\n\n\t\tconst tool = tools[toolCallName]\n\t\tif (!tool) {\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorType.UNKNOWN,\n\t\t\t\t`Tool \"${toolCallName}\" not found in tools`,\n\t\t\t\tnormalizedData\n\t\t\t)\n\t\t}\n\n\t\t// Extract and parse tool arguments\n\t\tconst argString = normalizedChoice.message?.tool_calls?.[0]?.function?.arguments\n\t\tif (!argString) {\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorType.INVALID_TOOL_ARGS,\n\t\t\t\t'No tool call arguments found',\n\t\t\t\tnormalizedData\n\t\t\t)\n\t\t}\n\n\t\tlet parsedArgs: unknown\n\t\ttry {\n\t\t\tparsedArgs = JSON.parse(argString)\n\t\t} catch (error) {\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorType.INVALID_TOOL_ARGS,\n\t\t\t\t'Failed to parse tool arguments as JSON',\n\t\t\t\terror\n\t\t\t)\n\t\t}\n\n\t\t// Validate with schema\n\t\tconst validation = tool.inputSchema.safeParse(parsedArgs)\n\t\tif (!validation.success) {\n\t\t\tconsole.error(validation.error)\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorType.INVALID_TOOL_ARGS,\n\t\t\t\t'Tool arguments validation failed',\n\t\t\t\tvalidation.error\n\t\t\t)\n\t\t}\n\t\tconst toolInput = validation.data\n\n\t\t// 5. Execute tool\n\t\tlet toolResult: unknown\n\t\ttry {\n\t\t\ttoolResult = await tool.execute(toolInput)\n\t\t} catch (e) {\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorType.TOOL_EXECUTION_ERROR,\n\t\t\t\t`Tool execution failed: ${(e as Error).message}`,\n\t\t\t\te\n\t\t\t)\n\t\t}\n\n\t\t// Return result\n\t\treturn {\n\t\t\ttoolCall: {\n\t\t\t\tname: toolCallName,\n\t\t\t\targs: toolInput,\n\t\t\t},\n\t\t\ttoolResult,\n\t\t\tusage: {\n\t\t\t\tpromptTokens: data.usage?.prompt_tokens ?? 0,\n\t\t\t\tcompletionTokens: data.usage?.completion_tokens ?? 0,\n\t\t\t\ttotalTokens: data.usage?.total_tokens ?? 0,\n\t\t\t\tcachedTokens: data.usage?.prompt_tokens_details?.cached_tokens,\n\t\t\t\treasoningTokens: data.usage?.completion_tokens_details?.reasoning_tokens,\n\t\t\t},\n\t\t\trawResponse: data,\n\t\t}\n\t}\n}\n","// Dev environment: use .env config if available, otherwise fallback to testing api\nexport const DEFAULT_MODEL_NAME: string =\n\timport.meta.env.DEV && import.meta.env.LLM_MODEL_NAME\n\t\t? import.meta.env.LLM_MODEL_NAME\n\t\t: 'PAGE-AGENT-FREE-TESTING-RANDOM'\n\nexport const DEFAULT_API_KEY: string =\n\timport.meta.env.DEV && import.meta.env.LLM_API_KEY\n\t\t? import.meta.env.LLM_API_KEY\n\t\t: 'PAGE-AGENT-FREE-TESTING-RANDOM'\n\nexport const DEFAULT_BASE_URL: string =\n\timport.meta.env.DEV && import.meta.env.LLM_BASE_URL\n\t\t? import.meta.env.LLM_BASE_URL\n\t\t: 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'\n\n// internal\n\nexport const LLM_MAX_RETRIES = 2\nexport const DEFAULT_TEMPERATURE = 0.7 // higher randomness helps auto-recovery\n","import { OpenAIClient } from './OpenAIClient'\nimport {\n\tDEFAULT_API_KEY,\n\tDEFAULT_BASE_URL,\n\tDEFAULT_MODEL_NAME,\n\tDEFAULT_TEMPERATURE,\n\tLLM_MAX_RETRIES,\n} from './constants'\nimport { InvokeError } from './errors'\nimport type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types'\n\nexport type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool }\n\nexport function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {\n\treturn {\n\t\tbaseURL: config.baseURL ?? DEFAULT_BASE_URL,\n\t\tapiKey: config.apiKey ?? DEFAULT_API_KEY,\n\t\tmodel: config.model ?? DEFAULT_MODEL_NAME,\n\t\ttemperature: config.temperature ?? DEFAULT_TEMPERATURE,\n\t\tmaxRetries: config.maxRetries ?? LLM_MAX_RETRIES,\n\t\tcustomFetch: (config.customFetch ?? fetch).bind(globalThis), // fetch will be illegal unless bound\n\t}\n}\n\nexport class LLM extends EventTarget {\n\tconfig: Required<LLMConfig>\n\tclient: LLMClient\n\n\tconstructor(config: LLMConfig) {\n\t\tsuper()\n\t\tthis.config = parseLLMConfig(config)\n\n\t\t// Default to OpenAI client\n\t\tthis.client = new OpenAIClient(this.config)\n\t}\n\n\t/**\n\t * - call llm api *once*\n\t * - invoke tool call *once*\n\t * - return the result of the tool\n\t */\n\tasync invoke(\n\t\tmessages: Message[],\n\t\ttools: Record<string, Tool>,\n\t\tabortSignal: AbortSignal,\n\t\toptions?: InvokeOptions\n\t): Promise<InvokeResult> {\n\t\treturn await withRetry(\n\t\t\tasync () => {\n\t\t\t\tconst result = await this.client.invoke(messages, tools, abortSignal, options)\n\n\t\t\t\treturn result\n\t\t\t},\n\t\t\t// retry settings\n\t\t\t{\n\t\t\t\tmaxRetries: this.config.maxRetries,\n\t\t\t\tonRetry: (current: number) => {\n\t\t\t\t\tthis.dispatchEvent(\n\t\t\t\t\t\tnew CustomEvent('retry', { detail: { current, max: this.config.maxRetries } })\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t\tonError: (error: Error) => {\n\t\t\t\t\tthis.dispatchEvent(new CustomEvent('error', { detail: { error } }))\n\t\t\t\t},\n\t\t\t}\n\t\t)\n\t}\n}\n\nasync function withRetry<T>(\n\tfn: () => Promise<T>,\n\tsettings: {\n\t\tmaxRetries: number\n\t\tonRetry: (retries: number) => void\n\t\tonError: (error: Error) => void\n\t}\n): Promise<T> {\n\tlet retries = 0\n\tlet lastError: Error | null = null\n\twhile (retries <= settings.maxRetries) {\n\t\tif (retries > 0) {\n\t\t\tsettings.onRetry(retries)\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 100))\n\t\t}\n\n\t\ttry {\n\t\t\treturn await fn()\n\t\t} catch (error: unknown) {\n\t\t\tconsole.error(error)\n\t\t\tsettings.onError(error as Error)\n\n\t\t\t// do not retry if aborted by user\n\t\t\tif ((error as { name?: string })?.name === 'AbortError') throw error\n\n\t\t\t// do not retry if error is not retryable (InvokeError)\n\t\t\tif (error instanceof InvokeError && !error.retryable) throw error\n\n\t\t\tlastError = error as Error\n\t\t\tretries++\n\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 100))\n\t\t}\n\t}\n\n\tthrow lastError!\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@page-agent/llms",
3
- "version": "0.0.20",
3
+ "version": "0.0.22",
4
4
  "type": "module",
5
5
  "main": "./dist/lib/page-agent-llms.js",
6
6
  "module": "./dist/lib/page-agent-llms.js",