@faapi/agent 6.31.0 → 6.32.0

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/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { LlmConfig, AgentToolDescriptor, FaapiContext, AgentCore, AgentMetadata, ToolMetadata, ToolModule, FaapiPlugin } from '@faapi/faapi';
1
+ import { LlmConfig, LlmComplete, AgentToolDescriptor, FaapiContext, AgentCore, AgentMetadata, ToolMetadata, ToolModule, FaapiPlugin } from '@faapi/faapi';
2
+ export { LlmComplete, LlmCompleteOptions } from '@faapi/faapi';
2
3
 
3
4
  /**
4
5
  * LLM Provider 错误
@@ -11,12 +12,31 @@ declare class LLMProviderError extends Error {
11
12
  readonly status?: number;
12
13
  /** 响应体摘要(前 500 字符,便于诊断) */
13
14
  readonly body?: string;
15
+ /**
16
+ * 实际发起的 HTTP 尝试次数(≥1,含失败尝试)
17
+ *
18
+ * 重试耗尽抛出时由重试循环回填(构造时未知,故非 readonly);
19
+ * 首次尝试即失败的确定性错误(4xx 直抛)为 1,未回填时视为 1。
20
+ */
21
+ attempts?: number;
14
22
  constructor(message: string, options?: {
15
23
  status?: number;
16
24
  body?: string;
17
25
  cause?: unknown;
18
26
  });
19
27
  }
28
+ /**
29
+ * LLM 请求超时
30
+ *
31
+ * {@link LLMProviderError} 的超时子类——`instanceof LLMTimeoutError` 可编程区分
32
+ * 「超时」与「网络错误」(两者 status 均为 undefined,仅靠 message 字符串无法区分)。
33
+ * 超时计入重试(与 429/5xx/网络错误同策略)。
34
+ */
35
+ declare class LLMTimeoutError extends LLMProviderError {
36
+ constructor(message: string, options?: {
37
+ cause?: unknown;
38
+ });
39
+ }
20
40
  /**
21
41
  * 创建 OpenAI 兼容 LLMProvider
22
42
  *
@@ -121,11 +141,23 @@ interface LLMCompleteRequest {
121
141
  temperature?: number;
122
142
  /** 最大生成 token 数 */
123
143
  maxTokens?: number;
144
+ /**
145
+ * 本次请求的超时(毫秒,调用级覆盖)
146
+ *
147
+ * 缺省回落 `LlmConfig.timeoutMs`,两者皆未设置时无超时。
148
+ * 超时触发抛 `LLMTimeoutError`(计入重试,与 429/5xx/网络错误同策略)。
149
+ */
150
+ timeoutMs?: number;
151
+ /**
152
+ * 本次请求的重试上限(调用级覆盖)
153
+ *
154
+ * 缺省回落 `LlmConfig.maxRetries`(默认 2)。仅 429/5xx/网络错误/超时计入重试。
155
+ */
156
+ maxRetries?: number;
124
157
  /**
125
158
  * 取消信号(透传到底层 HTTP 请求)
126
159
  *
127
- * abort 时请求中断并抛 `AgentAbortError`;与 `LlmConfig.timeoutMs` 的
128
- * 超时信号组合生效(任一触发即中断)。
160
+ * abort 时请求中断并抛 `AgentAbortError`;与超时信号组合生效(任一触发即中断)。
129
161
  */
130
162
  signal?: AbortSignal;
131
163
  }
@@ -168,6 +200,12 @@ interface LLMResponse {
168
200
  stopReason: LLMStopReason;
169
201
  /** token 用量(部分 provider 不返回) */
170
202
  usage?: LLMUsage;
203
+ /**
204
+ * 实际发起的 HTTP 尝试次数(≥1,含失败尝试)
205
+ *
206
+ * 供失败钩子/日志观测重试消耗;provider 未提供时视为 1。
207
+ */
208
+ attempts?: number;
171
209
  }
172
210
  /**
173
211
  * Token 用量(OpenAI chat completions 规范形)
@@ -228,6 +266,20 @@ interface LLMProvider {
228
266
  */
229
267
  declare function createProvider(config: LlmConfig): LLMProvider;
230
268
 
269
+ /**
270
+ * 创建轻量补全通道
271
+ *
272
+ * @param deps.llms provider 配置映射(来自 `config.agent.llms`,同源零新增配置)
273
+ * @param deps.providers 已构建的 provider 实例映射(插件传入——与 agent 循环共享
274
+ * 同一单例);缺省时按 `llms` 逐项 `createProvider` 现场构建
275
+ * (隔离 worker 重建 / 测试形态)
276
+ * @returns 补全通道(`complete` 方法)
277
+ */
278
+ declare function createLightComplete(deps: {
279
+ llms: Record<string, LlmConfig>;
280
+ providers?: Map<string, LLMProvider>;
281
+ }): LlmComplete;
282
+
231
283
  /**
232
284
  * 单次 agent.run() / agent.stream() 的结构化调用明细
233
285
  *
@@ -1096,19 +1148,8 @@ declare class Agent {
1096
1148
  */
1097
1149
  private resolveExternalProvider;
1098
1150
  /**
1099
- * 解析 `options.model` 字符串 key → provider + model
1100
- *
1101
- * 规则见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」。
1102
- * 无默认 provider——`key` 未传时用 agent 元数据 `config.model` 作为缺省 key;
1103
- * 两者皆无抛 `AgentError`(要求调用方传 `options.model` 或 `options.provider`)。
1104
- * 1. 精确匹配 `deps.providers` 的 key → 该 provider + 其 `models` 第一个 key
1105
- * (该 provider 未声明 `models` 时回落 `meta.model`)
1106
- * 2. 含 `/` → `provider/model` 形式,`deps.providers.get(provider)` + 该 model
1107
- * (要求该 model 在 `deps.llms[provider].models` 里)
1108
- * 3. 不含 `/` 且非 provider key → 在所有 provider 的 `models` 里按 model 名查找
1109
- * - 唯一 → 该 provider + 该 model
1110
- * - 多个 → 抛 `AgentError`(要求用 `provider/model` 消歧)
1111
- * - 无 → 抛 `AgentError`
1151
+ * 解析 `options.model` 字符串 key → provider + model(模块级 [resolveModelKey] 的
1152
+ * Agent 方法包装——meta.model 作缺省 key,与 [lightComplete](./lightComplete.md) 共用同一解析实现)
1112
1153
  *
1113
1154
  * @throws {AgentError} key 与 `meta.model` 均缺省;key 解析失败(provider/model
1114
1155
  * 不存在或歧义)
@@ -1294,4 +1335,4 @@ declare function createToolSchemaResolver(options?: {
1294
1335
  */
1295
1336
  declare const agentPlugin: FaapiPlugin;
1296
1337
 
1297
- export { Agent, AgentAbortError, type AgentDeps, AgentError, type AgentHandle, AgentRecursionError, type AgentRunOptions, type AgentRuntimeConfig, AgentToolTimeoutError, type AgentTrace, type AgentTraceEvent, type LLMCompleteRequest, type LLMMessage, type LLMProvider, LLMProviderError, type LLMResponse, type LLMStopReason, type LLMStreamChunk, type LLMToolCall, type LLMToolDefinition, type LLMUsage, type LlmCallEvent, type ReactLoopConfig, ReactLoopError, type ReactLoopResult, type ReactLoopStreamChunk, type SchemaSourceRef, type SubAgentCallEvent, type SubAgentDelta, type SubAgentDeltaEmitter, type SubAgentToolResult, type ToolCallEvent, type ToolExecutor, type ToolSchemaResolution, type TracingToolResult, createOpenAIProvider, createProvider, createToolSchemaResolver, agentPlugin as default, isSubAgentToolResult, isTracingToolResult, reactLoop, reactLoopStream };
1338
+ export { Agent, AgentAbortError, type AgentDeps, AgentError, type AgentHandle, AgentRecursionError, type AgentRunOptions, type AgentRuntimeConfig, AgentToolTimeoutError, type AgentTrace, type AgentTraceEvent, type LLMCompleteRequest, type LLMMessage, type LLMProvider, LLMProviderError, type LLMResponse, type LLMStopReason, type LLMStreamChunk, LLMTimeoutError, type LLMToolCall, type LLMToolDefinition, type LLMUsage, type LlmCallEvent, type ReactLoopConfig, ReactLoopError, type ReactLoopResult, type ReactLoopStreamChunk, type SchemaSourceRef, type SubAgentCallEvent, type SubAgentDelta, type SubAgentDeltaEmitter, type SubAgentToolResult, type ToolCallEvent, type ToolExecutor, type ToolSchemaResolution, type TracingToolResult, createLightComplete, createOpenAIProvider, createProvider, createToolSchemaResolver, agentPlugin as default, isSubAgentToolResult, isTracingToolResult, reactLoop, reactLoopStream };
package/dist/index.js CHANGED
@@ -14,6 +14,13 @@ var LLMProviderError = class extends Error {
14
14
  status;
15
15
  /** 响应体摘要(前 500 字符,便于诊断) */
16
16
  body;
17
+ /**
18
+ * 实际发起的 HTTP 尝试次数(≥1,含失败尝试)
19
+ *
20
+ * 重试耗尽抛出时由重试循环回填(构造时未知,故非 readonly);
21
+ * 首次尝试即失败的确定性错误(4xx 直抛)为 1,未回填时视为 1。
22
+ */
23
+ attempts;
17
24
  constructor(message, options) {
18
25
  super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
19
26
  this.name = "LLMProviderError";
@@ -21,6 +28,12 @@ var LLMProviderError = class extends Error {
21
28
  this.body = options?.body;
22
29
  }
23
30
  };
31
+ var LLMTimeoutError = class extends LLMProviderError {
32
+ constructor(message, options) {
33
+ super(message, options);
34
+ this.name = "LLMTimeoutError";
35
+ }
36
+ };
24
37
  function createOpenAIProvider(config) {
25
38
  const baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
26
39
  const apiKey = config.apiKey;
@@ -69,13 +82,19 @@ function createOpenAIProvider(config) {
69
82
  if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
70
83
  return headers;
71
84
  }
72
- function classifyAbortError(err, externalSignal) {
85
+ function resolveTimeoutMs(request) {
86
+ if (typeof request.timeoutMs === "number") return request.timeoutMs;
87
+ return typeof config.timeoutMs === "number" ? config.timeoutMs : void 0;
88
+ }
89
+ function classifyAbortError(err, externalSignal, timeoutMs) {
73
90
  if (externalSignal?.aborted) {
74
91
  return new AgentAbortError();
75
92
  }
76
93
  const errName = err?.name;
77
94
  if (errName === "AbortError" || errName === "TimeoutError") {
78
- return new LLMProviderError(`LLM request timed out after ${config.timeoutMs}ms`);
95
+ return new LLMTimeoutError(
96
+ timeoutMs !== void 0 ? `LLM request timed out after ${timeoutMs}ms` : "LLM request timed out"
97
+ );
79
98
  }
80
99
  const reason = err instanceof Error ? err.message : String(err);
81
100
  return new LLMProviderError(`Network error: ${reason}`, { cause: err });
@@ -84,11 +103,11 @@ function createOpenAIProvider(config) {
84
103
  const name = err?.name;
85
104
  return name === "AbortError" || name === "TimeoutError";
86
105
  }
87
- async function safeFetch(url, init, externalSignal) {
106
+ async function safeFetch(url, init, externalSignal, timeoutMs) {
88
107
  try {
89
108
  return await fetch(url, init);
90
109
  } catch (err) {
91
- throw classifyAbortError(err, externalSignal);
110
+ throw classifyAbortError(err, externalSignal, timeoutMs);
92
111
  }
93
112
  }
94
113
  function isRetryable(err) {
@@ -106,17 +125,23 @@ function createOpenAIProvider(config) {
106
125
  }
107
126
  function effectiveSignal(request) {
108
127
  const { signal } = request;
109
- const timeoutMs = typeof config.timeoutMs === "number" ? config.timeoutMs : void 0;
128
+ const timeoutMs = resolveTimeoutMs(request);
110
129
  if (!signal && !timeoutMs) return void 0;
111
130
  const signals = [];
112
131
  if (signal) signals.push(signal);
113
132
  if (timeoutMs) signals.push(AbortSignal.timeout(timeoutMs));
114
133
  return signals.length === 1 ? signals[0] : AbortSignal.any(signals);
115
134
  }
116
- async function fetchOkWithRetry(url, baseInit, request, maxRetries2) {
135
+ function resolveMaxRetries(request) {
136
+ if (typeof request.maxRetries === "number") return Math.max(0, request.maxRetries);
137
+ return maxRetries;
138
+ }
139
+ async function fetchOkWithRetry(url, baseInit, request) {
140
+ const maxAttemptsRetries = resolveMaxRetries(request);
141
+ const timeoutMs = resolveTimeoutMs(request);
117
142
  let lastErr;
118
143
  let lastResponse;
119
- for (let attempt = 0; attempt <= maxRetries2; attempt++) {
144
+ for (let attempt = 0; attempt <= maxAttemptsRetries; attempt++) {
120
145
  if (request.signal?.aborted) {
121
146
  throw new AgentAbortError();
122
147
  }
@@ -126,13 +151,13 @@ function createOpenAIProvider(config) {
126
151
  const init = { ...baseInit, signal: effectiveSignal(request) };
127
152
  let response;
128
153
  try {
129
- response = await safeFetch(url, init, request.signal);
154
+ response = await safeFetch(url, init, request.signal, timeoutMs);
130
155
  } catch (err2) {
131
156
  if (!isRetryable(err2)) throw err2;
132
157
  lastErr = err2;
133
158
  continue;
134
159
  }
135
- if (response.ok) return response;
160
+ if (response.ok) return { response, attempts: attempt + 1 };
136
161
  let excerpt = "<body unreadable>";
137
162
  try {
138
163
  excerpt = (await response.text()).slice(0, 500);
@@ -146,6 +171,9 @@ function createOpenAIProvider(config) {
146
171
  lastErr = err;
147
172
  lastResponse = response;
148
173
  }
174
+ if (lastErr instanceof LLMProviderError) {
175
+ lastErr.attempts = maxAttemptsRetries + 1;
176
+ }
149
177
  throw lastErr;
150
178
  }
151
179
  async function complete(request) {
@@ -156,13 +184,14 @@ function createOpenAIProvider(config) {
156
184
  headers: buildHeaders(),
157
185
  body: JSON.stringify(body)
158
186
  };
159
- const response = await fetchOkWithRetry(url, baseInit, request, maxRetries);
187
+ const timeoutMs = resolveTimeoutMs(request);
188
+ const { response, attempts } = await fetchOkWithRetry(url, baseInit, request);
160
189
  let bodyText;
161
190
  try {
162
191
  bodyText = await response.text();
163
192
  } catch (err) {
164
193
  if (!isAbortLike(err)) throw err;
165
- throw classifyAbortError(err, request.signal);
194
+ throw classifyAbortError(err, request.signal, timeoutMs);
166
195
  }
167
196
  let json;
168
197
  try {
@@ -192,7 +221,8 @@ function createOpenAIProvider(config) {
192
221
  return {
193
222
  message,
194
223
  stopReason: mapStopReason(choice.finish_reason ?? void 0),
195
- usage: mapUsage(json.usage)
224
+ usage: mapUsage(json.usage),
225
+ attempts
196
226
  };
197
227
  }
198
228
  async function* stream(request) {
@@ -204,7 +234,8 @@ function createOpenAIProvider(config) {
204
234
  headers: buildHeaders(),
205
235
  body: JSON.stringify(body)
206
236
  };
207
- const response = await fetchOkWithRetry(url, baseInit, request, maxRetries);
237
+ const timeoutMs = resolveTimeoutMs(request);
238
+ const { response } = await fetchOkWithRetry(url, baseInit, request);
208
239
  if (!response.body) {
209
240
  throw new LLMProviderError("Response body is null (streaming unsupported)", {
210
241
  status: response.status
@@ -281,7 +312,7 @@ function createOpenAIProvider(config) {
281
312
  yield finalizeStreamChunk(accumulators, finishReason, usage);
282
313
  } catch (err) {
283
314
  if (!isAbortLike(err)) throw err;
284
- throw classifyAbortError(err, request.signal);
315
+ throw classifyAbortError(err, request.signal, timeoutMs);
285
316
  } finally {
286
317
  try {
287
318
  await reader.cancel();
@@ -433,6 +464,9 @@ function createProvider(config) {
433
464
  }
434
465
  }
435
466
 
467
+ // src/agent.ts
468
+ import { readResource, subAgentToolName } from "@faapi/faapi";
469
+
436
470
  // src/trace.ts
437
471
  function isTracingToolResult(value) {
438
472
  return typeof value === "object" && value !== null && value.__trace === true;
@@ -958,7 +992,6 @@ async function* reactLoopStream(input, config) {
958
992
  }
959
993
 
960
994
  // src/agent.ts
961
- import { readResource, subAgentToolName } from "@faapi/faapi";
962
995
  var DEFAULT_MAX_AGENT_DEPTH = 3;
963
996
  var DEFAULT_SUBAGENT_INPUT_DESCRIPTION = "\u6D3E\u53D1\u7ED9\u8BE5 agent \u7684\u4EFB\u52A1\u4EA4\u63A5\u5355\uFF08\u81EA\u7136\u8BED\u8A00\uFF09\uFF1A\u5199\u6E05\u4EFB\u52A1\u76EE\u6807\u3001\u5FC5\u8981\u7684\u4E0A\u4E0B\u6587\u4FE1\u606F\u4E0E\u5BF9\u4EA7\u51FA\u7ED3\u679C\u7684\u8981\u6C42";
964
997
  function extractSubAgentUserInput(args) {
@@ -984,6 +1017,64 @@ function materializeProvider(external) {
984
1017
  'options.provider must be an LlmConfig object (with a string "provider" field) or an LLMProvider instance (with complete/stream methods)'
985
1018
  );
986
1019
  }
1020
+ function resolveModelKey(key, deps, fallbackKey) {
1021
+ const effectiveKey = key ?? fallbackKey;
1022
+ if (effectiveKey === void 0) {
1023
+ throw new AgentError(
1024
+ 'No LLM provider resolved: pass options.model (a provider key / "provider/model" / model name from config.agent.llms), options.provider (external provider), or declare model in the agent config'
1025
+ );
1026
+ }
1027
+ const byProviderKey = deps.providers.get(effectiveKey);
1028
+ if (byProviderKey) {
1029
+ const llmConfig = deps.llms[effectiveKey];
1030
+ const firstModel = llmConfig ? Object.keys(llmConfig.models)[0] : void 0;
1031
+ return {
1032
+ provider: byProviderKey,
1033
+ providerName: effectiveKey,
1034
+ model: firstModel ?? fallbackKey
1035
+ };
1036
+ }
1037
+ if (effectiveKey.includes("/")) {
1038
+ const slashIdx = effectiveKey.indexOf("/");
1039
+ const providerName = effectiveKey.slice(0, slashIdx);
1040
+ const modelName = effectiveKey.slice(slashIdx + 1);
1041
+ const provider = deps.providers.get(providerName);
1042
+ if (!provider) {
1043
+ throw new AgentError(
1044
+ `Unknown provider "${providerName}" in model key "${effectiveKey}". Declare it in config.agent.llms, or pass options.provider to use an external provider.`
1045
+ );
1046
+ }
1047
+ const llmConfig = deps.llms[providerName];
1048
+ if (!llmConfig || !llmConfig.models[modelName]) {
1049
+ throw new AgentError(
1050
+ `Model "${modelName}" not found in provider "${providerName}". Declare it in config.agent.llms.${providerName}.models.`
1051
+ );
1052
+ }
1053
+ return { provider, providerName, model: modelName };
1054
+ }
1055
+ const matches = [];
1056
+ for (const [providerName, provider] of deps.providers) {
1057
+ const llmConfig = deps.llms[providerName];
1058
+ if (llmConfig && llmConfig.models[effectiveKey]) {
1059
+ matches.push({ provider, providerName });
1060
+ }
1061
+ }
1062
+ if (matches.length === 1) {
1063
+ return {
1064
+ provider: matches[0].provider,
1065
+ providerName: matches[0].providerName,
1066
+ model: effectiveKey
1067
+ };
1068
+ }
1069
+ if (matches.length > 1) {
1070
+ throw new AgentError(
1071
+ `Model "${effectiveKey}" is ambiguous (found in providers: ${matches.map((m) => m.providerName).join(", ")}). Use "provider/model" to disambiguate.`
1072
+ );
1073
+ }
1074
+ throw new AgentError(
1075
+ `Model "${effectiveKey}" not found in any provider. Declare it in config.agent.llms.*.models, or pass options.provider to use an external provider.`
1076
+ );
1077
+ }
987
1078
  function validateResumeHistory(messages) {
988
1079
  messages.forEach((message, index) => {
989
1080
  if (!VALID_MESSAGE_ROLES.has(message.role)) {
@@ -1286,72 +1377,14 @@ var Agent = class _Agent {
1286
1377
  return { provider, model };
1287
1378
  }
1288
1379
  /**
1289
- * 解析 `options.model` 字符串 key → provider + model
1290
- *
1291
- * 规则见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」。
1292
- * 无默认 provider——`key` 未传时用 agent 元数据 `config.model` 作为缺省 key;
1293
- * 两者皆无抛 `AgentError`(要求调用方传 `options.model` 或 `options.provider`)。
1294
- * 1. 精确匹配 `deps.providers` 的 key → 该 provider + 其 `models` 第一个 key
1295
- * (该 provider 未声明 `models` 时回落 `meta.model`)
1296
- * 2. 含 `/` → `provider/model` 形式,`deps.providers.get(provider)` + 该 model
1297
- * (要求该 model 在 `deps.llms[provider].models` 里)
1298
- * 3. 不含 `/` 且非 provider key → 在所有 provider 的 `models` 里按 model 名查找
1299
- * - 唯一 → 该 provider + 该 model
1300
- * - 多个 → 抛 `AgentError`(要求用 `provider/model` 消歧)
1301
- * - 无 → 抛 `AgentError`
1380
+ * 解析 `options.model` 字符串 key → provider + model(模块级 [resolveModelKey] 的
1381
+ * Agent 方法包装——meta.model 作缺省 key,与 [lightComplete](./lightComplete.md) 共用同一解析实现)
1302
1382
  *
1303
1383
  * @throws {AgentError} key 与 `meta.model` 均缺省;key 解析失败(provider/model
1304
1384
  * 不存在或歧义)
1305
1385
  */
1306
1386
  resolveModelKey(key, meta) {
1307
- const effectiveKey = key ?? meta.model;
1308
- if (effectiveKey === void 0) {
1309
- throw new AgentError(
1310
- 'No LLM provider resolved: pass options.model (a provider key / "provider/model" / model name from config.agent.llms), options.provider (external provider), or declare model in the agent config'
1311
- );
1312
- }
1313
- const byProviderKey = this.deps.providers.get(effectiveKey);
1314
- if (byProviderKey) {
1315
- const llmConfig = this.deps.llms[effectiveKey];
1316
- const firstModel = llmConfig ? Object.keys(llmConfig.models)[0] : void 0;
1317
- return { provider: byProviderKey, model: firstModel ?? meta.model };
1318
- }
1319
- if (effectiveKey.includes("/")) {
1320
- const slashIdx = effectiveKey.indexOf("/");
1321
- const providerName = effectiveKey.slice(0, slashIdx);
1322
- const modelName = effectiveKey.slice(slashIdx + 1);
1323
- const provider = this.deps.providers.get(providerName);
1324
- if (!provider) {
1325
- throw new AgentError(
1326
- `Unknown provider "${providerName}" in model key "${effectiveKey}". Declare it in config.agent.llms, or pass options.provider to use an external provider.`
1327
- );
1328
- }
1329
- const llmConfig = this.deps.llms[providerName];
1330
- if (!llmConfig || !llmConfig.models[modelName]) {
1331
- throw new AgentError(
1332
- `Model "${modelName}" not found in provider "${providerName}". Declare it in config.agent.llms.${providerName}.models.`
1333
- );
1334
- }
1335
- return { provider, model: modelName };
1336
- }
1337
- const matches = [];
1338
- for (const [providerName, provider] of this.deps.providers) {
1339
- const llmConfig = this.deps.llms[providerName];
1340
- if (llmConfig && llmConfig.models[effectiveKey]) {
1341
- matches.push({ provider, providerName });
1342
- }
1343
- }
1344
- if (matches.length === 1) {
1345
- return { provider: matches[0].provider, model: effectiveKey };
1346
- }
1347
- if (matches.length > 1) {
1348
- throw new AgentError(
1349
- `Model "${effectiveKey}" is ambiguous (found in providers: ${matches.map((m) => m.providerName).join(", ")}). Use "provider/model" to disambiguate.`
1350
- );
1351
- }
1352
- throw new AgentError(
1353
- `Model "${effectiveKey}" not found in any provider. Declare it in config.agent.llms.*.models, or pass options.provider to use an external provider.`
1354
- );
1387
+ return resolveModelKey(key, this.deps, meta.model);
1355
1388
  }
1356
1389
  /**
1357
1390
  * 组装 LLM 可见 tool 列表(OpenAI chat completions 规范形)
@@ -1623,6 +1656,72 @@ var Agent = class _Agent {
1623
1656
  }
1624
1657
  };
1625
1658
 
1659
+ // src/lightComplete.ts
1660
+ var DEFAULT_LIGHT_TIMEOUT_MS = 6e4;
1661
+ function createLightComplete(deps) {
1662
+ const llms = deps.llms;
1663
+ const providers = deps.providers ?? buildProviders(llms);
1664
+ return {
1665
+ async complete(input, options) {
1666
+ const defaultKey = Object.keys(llms)[0];
1667
+ const { provider, providerName, model } = resolveModelKey(
1668
+ options?.model,
1669
+ { providers, llms },
1670
+ defaultKey
1671
+ );
1672
+ const messages = options?.system ? [
1673
+ { role: "system", content: options.system },
1674
+ { role: "user", content: input }
1675
+ ] : [{ role: "user", content: input }];
1676
+ const llmConfig = llms[providerName];
1677
+ const configTimeout = typeof llmConfig?.timeoutMs === "number" ? llmConfig.timeoutMs : void 0;
1678
+ const timeoutMs = options?.timeoutMs ?? configTimeout ?? DEFAULT_LIGHT_TIMEOUT_MS;
1679
+ try {
1680
+ const response = await provider.complete({
1681
+ messages,
1682
+ model,
1683
+ ...options?.temperature !== void 0 ? { temperature: options.temperature } : {},
1684
+ ...options?.maxTokens !== void 0 ? { maxTokens: options.maxTokens } : {},
1685
+ ...options?.signal !== void 0 ? { signal: options.signal } : {},
1686
+ timeoutMs,
1687
+ ...options?.maxRetries !== void 0 ? { maxRetries: options.maxRetries } : {}
1688
+ });
1689
+ return response.message.content ?? "";
1690
+ } catch (err) {
1691
+ if (err instanceof AgentAbortError) throw err;
1692
+ if (!(err instanceof LLMProviderError)) throw err;
1693
+ return handleFailure(err, options);
1694
+ }
1695
+ }
1696
+ };
1697
+ }
1698
+ function buildProviders(llms) {
1699
+ const providers = /* @__PURE__ */ new Map();
1700
+ for (const [name, llmConfig] of Object.entries(llms)) {
1701
+ providers.set(name, createProvider(llmConfig));
1702
+ }
1703
+ return providers;
1704
+ }
1705
+ function handleFailure(err, options) {
1706
+ const { onFailure, fallback } = options ?? {};
1707
+ const attempts = err.attempts ?? 1;
1708
+ if (onFailure) {
1709
+ try {
1710
+ onFailure(err, { attempts });
1711
+ } catch {
1712
+ }
1713
+ }
1714
+ if (fallback !== void 0) {
1715
+ if (!onFailure) {
1716
+ console.warn(
1717
+ `[faapi/agent] llm.complete fallback engaged after ${attempts} attempt(s): ${err.message}`
1718
+ );
1719
+ }
1720
+ return Promise.resolve(fallback);
1721
+ }
1722
+ return Promise.reject(err);
1723
+ }
1724
+
1626
1725
  // src/toolSchemaResolver.ts
1627
1726
  import { statSync } from "fs";
1628
1727
  import { z } from "zod";
@@ -1692,6 +1791,7 @@ var agentPlugin = {
1692
1791
  }
1693
1792
  providers.set(name, createProvider(llmConfig));
1694
1793
  }
1794
+ ctx.registries.llm.register(createLightComplete({ llms, providers }));
1695
1795
  const runtimeConfig = {
1696
1796
  maxTurns: agentConfig?.maxTurns,
1697
1797
  maxAgentDepth: agentConfig?.maxAgentDepth,
@@ -1739,7 +1839,9 @@ export {
1739
1839
  AgentRecursionError,
1740
1840
  AgentToolTimeoutError,
1741
1841
  LLMProviderError,
1842
+ LLMTimeoutError,
1742
1843
  ReactLoopError,
1844
+ createLightComplete,
1743
1845
  createOpenAIProvider,
1744
1846
  createProvider,
1745
1847
  createToolSchemaResolver,