@page-agent/llms 0.0.14 → 0.0.16

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.
@@ -54,7 +54,7 @@ export declare interface LLMClient {
54
54
  }
55
55
 
56
56
  /**
57
- * LLM configuration for PageAgent
57
+ * LLM configuration
58
58
  */
59
59
  export declare interface LLMConfig {
60
60
  baseURL?: string;
@@ -63,6 +63,12 @@ export declare interface LLMConfig {
63
63
  temperature?: number;
64
64
  maxTokens?: number;
65
65
  maxRetries?: number;
66
+ /**
67
+ * Custom fetch function for LLM API requests.
68
+ * Use this to customize headers, credentials, proxy, etc.
69
+ * The response should follow OpenAI API format.
70
+ */
71
+ customFetch?: typeof globalThis.fetch;
66
72
  }
67
73
 
68
74
  /**
@@ -2,12 +2,6 @@ var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
  import chalk from "chalk";
4
4
  import { z } from "zod";
5
- const DEFAULT_MODEL_NAME = "PAGE-AGENT-FREE-TESTING-RANDOM";
6
- const DEFAULT_API_KEY = "PAGE-AGENT-FREE-TESTING-RANDOM";
7
- const DEFAULT_BASE_URL = "https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy";
8
- const LLM_MAX_RETRIES = 2;
9
- const DEFAULT_TEMPERATURE = 0.7;
10
- const DEFAULT_MAX_TOKENS = 4096;
11
5
  const InvokeErrorType = {
12
6
  // Retryable
13
7
  NETWORK_ERROR: "network_error",
@@ -58,6 +52,10 @@ const _InvokeError = class _InvokeError extends Error {
58
52
  };
59
53
  __name(_InvokeError, "InvokeError");
60
54
  let InvokeError = _InvokeError;
55
+ function debug(message) {
56
+ console.debug(chalk.gray("[LLM]"), message);
57
+ }
58
+ __name(debug, "debug");
61
59
  function zodToOpenAITool(name, tool) {
62
60
  return {
63
61
  type: "function",
@@ -192,30 +190,63 @@ function lenientParseMacroToolCall(responseData, inputSchema) {
192
190
  __name(lenientParseMacroToolCall, "lenientParseMacroToolCall");
193
191
  function modelPatch(body) {
194
192
  const model = body.model || "";
195
- if (model.toLowerCase().startsWith("claude")) {
193
+ if (!model) return body;
194
+ const modelName = normalizeModelName(model);
195
+ if (modelName.startsWith("claude")) {
196
+ debug("Applying Claude patch: change tool_choice and disable thinking");
196
197
  body.tool_choice = { type: "tool", name: "AgentOutput" };
197
198
  body.thinking = { type: "disabled" };
198
199
  }
199
- if (model.toLowerCase().includes("grok")) {
200
- console.log("Applying Grok patch: removing tool_choice");
200
+ if (modelName.startsWith("grok")) {
201
+ debug("Applying Grok patch: removing tool_choice");
201
202
  delete body.tool_choice;
202
- console.log("Applying Grok patch: disable reasoning and thinking");
203
+ debug("Applying Grok patch: disable reasoning and thinking");
203
204
  body.thinking = { type: "disabled", effort: "minimal" };
204
205
  body.reasoning = { enabled: false, effort: "low" };
205
206
  }
207
+ if (modelName.startsWith("gpt")) {
208
+ debug("Applying GPT patch: set verbosity to low");
209
+ body.verbosity = "low";
210
+ if (modelName.startsWith("gpt-52")) {
211
+ debug("Applying GPT-52 patch: disable reasoning");
212
+ body.reasoning_effort = "none";
213
+ } else if (modelName.startsWith("gpt-51")) {
214
+ debug("Applying GPT-51 patch: disable reasoning");
215
+ body.reasoning_effort = "none";
216
+ } else if (modelName.startsWith("gpt-5")) {
217
+ debug("Applying GPT-5 patch: set reasoning effort to low");
218
+ body.reasoning_effort = "low";
219
+ }
220
+ }
221
+ if (modelName.startsWith("gemini")) {
222
+ debug("Applying Gemini patch: set reasoning effort to minimal");
223
+ body.reasoning_effort = "minimal";
224
+ }
206
225
  return body;
207
226
  }
208
227
  __name(modelPatch, "modelPatch");
228
+ function normalizeModelName(modelName) {
229
+ let normalizedName = modelName.toLowerCase();
230
+ if (normalizedName.includes("/")) {
231
+ normalizedName = normalizedName.split("/")[1];
232
+ }
233
+ normalizedName = normalizedName.replace(/_/g, "");
234
+ normalizedName = normalizedName.replace(/\./g, "");
235
+ return normalizedName;
236
+ }
237
+ __name(normalizeModelName, "normalizeModelName");
209
238
  const _OpenAIClient = class _OpenAIClient {
210
239
  config;
240
+ fetch;
211
241
  constructor(config) {
212
242
  this.config = config;
243
+ this.fetch = config.customFetch;
213
244
  }
214
245
  async invoke(messages, tools, abortSignal) {
215
246
  const openaiTools = Object.entries(tools).map(([name, tool2]) => zodToOpenAITool(name, tool2));
216
247
  let response;
217
248
  try {
218
- response = await fetch(`${this.config.baseURL}/chat/completions`, {
249
+ response = await this.fetch(`${this.config.baseURL}/chat/completions`, {
219
250
  method: "POST",
220
251
  headers: {
221
252
  "Content-Type": "application/json",
@@ -230,9 +261,6 @@ const _OpenAIClient = class _OpenAIClient {
230
261
  tools: openaiTools,
231
262
  // tool_choice: 'required',
232
263
  tool_choice: { type: "function", function: { name: "AgentOutput" } },
233
- // model specific params
234
- // reasoning_effort: 'minimal',
235
- // verbosity: 'low',
236
264
  parallel_tool_calls: false
237
265
  })
238
266
  ),
@@ -304,6 +332,12 @@ const _OpenAIClient = class _OpenAIClient {
304
332
  };
305
333
  __name(_OpenAIClient, "OpenAIClient");
306
334
  let OpenAIClient = _OpenAIClient;
335
+ const DEFAULT_MODEL_NAME = "PAGE-AGENT-FREE-TESTING-RANDOM";
336
+ const DEFAULT_API_KEY = "PAGE-AGENT-FREE-TESTING-RANDOM";
337
+ const DEFAULT_BASE_URL = "https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy";
338
+ const LLM_MAX_RETRIES = 2;
339
+ const DEFAULT_TEMPERATURE = 0.7;
340
+ const DEFAULT_MAX_TOKENS = 16e3;
307
341
  function parseLLMConfig(config) {
308
342
  return {
309
343
  baseURL: config.baseURL ?? DEFAULT_BASE_URL,
@@ -311,7 +345,8 @@ function parseLLMConfig(config) {
311
345
  model: config.model ?? DEFAULT_MODEL_NAME,
312
346
  temperature: config.temperature ?? DEFAULT_TEMPERATURE,
313
347
  maxTokens: config.maxTokens ?? DEFAULT_MAX_TOKENS,
314
- maxRetries: config.maxRetries ?? LLM_MAX_RETRIES
348
+ maxRetries: config.maxRetries ?? LLM_MAX_RETRIES,
349
+ customFetch: config.customFetch ?? globalThis.fetch
315
350
  };
316
351
  }
317
352
  __name(parseLLMConfig, "parseLLMConfig");
@@ -321,13 +356,7 @@ const _LLM = class _LLM extends EventTarget {
321
356
  constructor(config) {
322
357
  super();
323
358
  this.config = parseLLMConfig(config);
324
- this.client = new OpenAIClient({
325
- model: this.config.model,
326
- apiKey: this.config.apiKey,
327
- baseURL: this.config.baseURL,
328
- temperature: this.config.temperature,
329
- maxTokens: this.config.maxTokens
330
- });
359
+ this.client = new OpenAIClient(this.config);
331
360
  }
332
361
  /**
333
362
  * - call llm api *once*
@@ -1 +1 @@
1
- {"version":3,"file":"page-agent-llms.js","sources":["../../src/constants.ts","../../src/errors.ts","../../src/utils.ts","../../src/OpenAILenientClient.ts","../../src/index.ts"],"sourcesContent":["// 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\nexport const DEFAULT_MAX_TOKENS = 4096\n","/**\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\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\nexport function modelPatch(body: Record<string, any>) {\n\tconst model: string = body.model || ''\n\n\tif (model.toLowerCase().startsWith('claude')) {\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 (model.toLowerCase().includes('grok')) {\n\t\tconsole.log('Applying Grok patch: removing tool_choice')\n\t\tdelete body.tool_choice\n\t\tconsole.log('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\treturn body\n}\n","/**\n * OpenAI Client implementation\n */\nimport { InvokeError, InvokeErrorType } from './errors'\nimport type {\n\tInvokeResult,\n\tLLMClient,\n\tMacroToolInput,\n\tMessage,\n\tOpenAIClientConfig,\n\tTool,\n} from './types'\nimport { lenientParseMacroToolCall, modelPatch, zodToOpenAITool } from './utils'\n\nexport class OpenAIClient implements LLMClient {\n\tconfig: OpenAIClientConfig\n\n\tconstructor(config: OpenAIClientConfig) {\n\t\tthis.config = config\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 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\tmax_tokens: this.config.maxTokens,\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\n\t\t\t\t\t\t// model specific params\n\n\t\t\t\t\t\t// reasoning_effort: 'minimal',\n\t\t\t\t\t\t// verbosity: 'low',\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\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","/**\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 {\n\tDEFAULT_API_KEY,\n\tDEFAULT_BASE_URL,\n\tDEFAULT_MAX_TOKENS,\n\tDEFAULT_MODEL_NAME,\n\tDEFAULT_TEMPERATURE,\n\tLLM_MAX_RETRIES,\n} from './constants'\nimport { InvokeError } from './errors'\nimport { OpenAIClient } from './OpenAILenientClient'\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\tmaxTokens: config.maxTokens ?? DEFAULT_MAX_TOKENS,\n\t\tmaxRetries: config.maxRetries ?? LLM_MAX_RETRIES,\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({\n\t\t\tmodel: this.config.model,\n\t\t\tapiKey: this.config.apiKey,\n\t\t\tbaseURL: this.config.baseURL,\n\t\t\ttemperature: this.config.temperature,\n\t\t\tmaxTokens: this.config.maxTokens,\n\t\t})\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/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\tmax_tokens: this.config.maxTokens,\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\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\nexport const DEFAULT_MAX_TOKENS = 16000\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_MAX_TOKENS,\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\tmaxTokens: config.maxTokens ?? DEFAULT_MAX_TOKENS,\n\t\tmaxRetries: config.maxRetries ?? LLM_MAX_RETRIES,\n\t\tcustomFetch: config.customFetch ?? globalThis.fetch,\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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@page-agent/llms",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "type": "module",
5
5
  "main": "./dist/lib/page-agent-llms.js",
6
6
  "module": "./dist/lib/page-agent-llms.js",
@@ -38,6 +38,6 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "chalk": "^5.6.2",
41
- "zod": "^4.2.0"
41
+ "zod": "^4.2.1"
42
42
  }
43
43
  }