@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.
package/dist/lib/index.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ export declare interface LLMClient {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
* LLM configuration
|
|
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
|
|
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 (
|
|
200
|
-
|
|
200
|
+
if (modelName.startsWith("grok")) {
|
|
201
|
+
debug("Applying Grok patch: removing tool_choice");
|
|
201
202
|
delete body.tool_choice;
|
|
202
|
-
|
|
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.
|
|
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.
|
|
41
|
+
"zod": "^4.2.1"
|
|
42
42
|
}
|
|
43
43
|
}
|