@faapi/agent 6.30.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 +118 -30
- package/dist/index.js +255 -101
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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 <=
|
|
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
|
|
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
|
|
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
|
-
|
|
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 规范形)
|
|
@@ -1364,9 +1397,15 @@ var Agent = class _Agent {
|
|
|
1364
1397
|
* - `resolveToolSchema` 提供 → 用其 `jsonSchema`
|
|
1365
1398
|
* - 未提供 / tool 无 `inputTypeName` → 自由 schema `{ type: 'object' }`
|
|
1366
1399
|
*
|
|
1367
|
-
* sub-agent 的 `function.parameters`
|
|
1368
|
-
*
|
|
1369
|
-
*
|
|
1400
|
+
* sub-agent 的 `function.parameters` 按「派发入参 schema 声明」二选一:
|
|
1401
|
+
* - **富 schema 模式**——sub 元数据声明 `inputTypeName`(handler.ts 顶层 `Input`
|
|
1402
|
+
* 导出)且 `resolveAgentInputSchema` 已接线 → 用解析出的 JSON Schema(结构性
|
|
1403
|
+
* 交接单,字段 JSDoc 即主控可见参数描述)。声明了但解析为 `undefined` 是产物
|
|
1404
|
+
* 异常(dev/prod 全量生成下 zod.js 不可能合法缺失),抛 `AgentError` 不静默降级
|
|
1405
|
+
* - **单字段 `input` 模式**——未声明 `Input` 或 resolver 未接线 → 显式单字段
|
|
1406
|
+
* schema(string,必填)——严格遵循 JSON schema 的模型对无属性 `{ type: 'object' }`
|
|
1407
|
+
* 只回 `{}`,派发上下文传不进子代理;description 用 sub 元数据 `inputDescription`,
|
|
1408
|
+
* 未声明用默认文案
|
|
1370
1409
|
*/
|
|
1371
1410
|
async buildToolDefinitions(agentName) {
|
|
1372
1411
|
const definitions = /* @__PURE__ */ new Map();
|
|
@@ -1390,16 +1429,7 @@ var Agent = class _Agent {
|
|
|
1390
1429
|
function: {
|
|
1391
1430
|
name,
|
|
1392
1431
|
description: subAgent.description,
|
|
1393
|
-
parameters:
|
|
1394
|
-
type: "object",
|
|
1395
|
-
properties: {
|
|
1396
|
-
input: {
|
|
1397
|
-
type: "string",
|
|
1398
|
-
description: subAgent.inputDescription ?? DEFAULT_SUBAGENT_INPUT_DESCRIPTION
|
|
1399
|
-
}
|
|
1400
|
-
},
|
|
1401
|
-
required: ["input"]
|
|
1402
|
-
}
|
|
1432
|
+
parameters: await this.getSubAgentParameters(subAgent)
|
|
1403
1433
|
}
|
|
1404
1434
|
});
|
|
1405
1435
|
}
|
|
@@ -1407,6 +1437,39 @@ var Agent = class _Agent {
|
|
|
1407
1437
|
const filtered = this.deps.config?.filterTools?.(defs, this.deps.ctx);
|
|
1408
1438
|
return filtered ?? defs;
|
|
1409
1439
|
}
|
|
1440
|
+
/**
|
|
1441
|
+
* 解析 sub-agent 派发工具的 parameters(富 schema / 单字段 input 二选一)
|
|
1442
|
+
*
|
|
1443
|
+
* 富 schema 判定需要完整元数据的 `inputTypeName`(`AgentCore` 不含)——经
|
|
1444
|
+
* `getAgentEntry` 查询;未声明或 `resolveAgentInputSchema` 未接线时返回单字段
|
|
1445
|
+
* `input` schema(历史行为,完全向后兼容)。`inputDescription` 仍从 `AgentCore`
|
|
1446
|
+
* 读取(LLM 可见字段的既定来源,DB skill 同样可声明)。
|
|
1447
|
+
*
|
|
1448
|
+
* @throws {AgentError} 声明了 `inputTypeName` 且 resolver 已接线但解析为
|
|
1449
|
+
* `undefined`(zod.js 缺失/损坏的产物异常,不静默退回单字段模式)
|
|
1450
|
+
*/
|
|
1451
|
+
async getSubAgentParameters(subAgent) {
|
|
1452
|
+
const entry = this.deps.getAgentEntry(subAgent.name);
|
|
1453
|
+
if (!entry?.inputTypeName || !this.deps.resolveAgentInputSchema) {
|
|
1454
|
+
return {
|
|
1455
|
+
type: "object",
|
|
1456
|
+
properties: {
|
|
1457
|
+
input: {
|
|
1458
|
+
type: "string",
|
|
1459
|
+
description: subAgent.inputDescription ?? DEFAULT_SUBAGENT_INPUT_DESCRIPTION
|
|
1460
|
+
}
|
|
1461
|
+
},
|
|
1462
|
+
required: ["input"]
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
const schemaRes = await this.deps.resolveAgentInputSchema(entry);
|
|
1466
|
+
if (!schemaRes) {
|
|
1467
|
+
throw new AgentError(
|
|
1468
|
+
`Sub-agent "${subAgent.name}" declares input schema "${entry.inputTypeName}" but its zod.js artifact is missing or broken (run \`faapi build\` / restart \`faapi dev\` to regenerate)`
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
return schemaRes.jsonSchema;
|
|
1472
|
+
}
|
|
1410
1473
|
/**
|
|
1411
1474
|
* tool 执行路由(由 reactLoop 调用)
|
|
1412
1475
|
*
|
|
@@ -1482,9 +1545,15 @@ var Agent = class _Agent {
|
|
|
1482
1545
|
* sub-agent 递归执行
|
|
1483
1546
|
*
|
|
1484
1547
|
* 1. `maxAgentDepth` 防护——超限抛 {@link AgentRecursionError}
|
|
1485
|
-
* 2. sub
|
|
1548
|
+
* 2. **派发入参校验(富 schema 模式)**——sub 元数据声明 `inputTypeName` 且
|
|
1549
|
+
* `resolveAgentInputSchema` 已接线时执行前 `validate(args)`:失败返回
|
|
1550
|
+
* `{ error }` 回灌主控 LLM 重试(与常规 tool 校验失败同语义,不进入子循环——
|
|
1551
|
+
* 省掉一次注定失败的子 agent 轮次);通过后以 coerce 后的 value 继续传导。
|
|
1552
|
+
* 声明了但解析为 `undefined`(zod.js 缺失/损坏)抛 `AgentError` 显式失败;
|
|
1553
|
+
* resolver 未接线或未声明 `Input` 时跳过校验,行为与历史版本一致
|
|
1554
|
+
* 3. sub-agent handler 导出 `run` 时调自定义 `mod.run(args)`(无 trace、无结构化
|
|
1486
1555
|
* usage 可卷——直接返回业务结果,其 token 不进入父 run 台账)
|
|
1487
|
-
*
|
|
1556
|
+
* 4. 无 `run` 时调 `subAgent.run(stringify(args), { agent, provider, model, enableTracing })`
|
|
1488
1557
|
* 走默认 reactLoop——继承父调用的 provider,sub 元数据声明 `model` 时优先用自身的,
|
|
1489
1558
|
* 未声明时沿用父 model
|
|
1490
1559
|
*
|
|
@@ -1499,12 +1568,27 @@ var Agent = class _Agent {
|
|
|
1499
1568
|
* user 消息:args 恰为单字段 `{ input: <string> }`(与显式入参 schema 形状一致)
|
|
1500
1569
|
* 时直传字符串,其余形状 stringify 兜底(见 `extractSubAgentUserInput`)。
|
|
1501
1570
|
*/
|
|
1502
|
-
async executeSubAgent(subName,
|
|
1571
|
+
async executeSubAgent(subName, rawArgs, callCtx, deltaEmitter) {
|
|
1503
1572
|
const newDepth = this.depth + 1;
|
|
1504
1573
|
const maxDepth = this.deps.config?.maxAgentDepth ?? DEFAULT_MAX_AGENT_DEPTH;
|
|
1505
1574
|
if (newDepth > maxDepth) {
|
|
1506
1575
|
throw new AgentRecursionError(maxDepth, newDepth);
|
|
1507
1576
|
}
|
|
1577
|
+
let args = rawArgs;
|
|
1578
|
+
const entry = this.deps.getAgentEntry(subName);
|
|
1579
|
+
if (entry?.inputTypeName && this.deps.resolveAgentInputSchema) {
|
|
1580
|
+
const schemaRes = await this.deps.resolveAgentInputSchema(entry);
|
|
1581
|
+
if (!schemaRes) {
|
|
1582
|
+
throw new AgentError(
|
|
1583
|
+
`Sub-agent "${subName}" declares input schema "${entry.inputTypeName}" but its zod.js artifact is missing or broken (run \`faapi build\` / restart \`faapi dev\` to regenerate)`
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
const result2 = schemaRes.validate(args);
|
|
1587
|
+
if (!result2.ok) {
|
|
1588
|
+
return { error: result2.error };
|
|
1589
|
+
}
|
|
1590
|
+
args = result2.value ?? args;
|
|
1591
|
+
}
|
|
1508
1592
|
const toolName = subAgentToolName(subName);
|
|
1509
1593
|
const subAgent = new _Agent(this.deps, newDepth);
|
|
1510
1594
|
const subMeta = this.deps.getAgent(subName);
|
|
@@ -1572,12 +1656,78 @@ var Agent = class _Agent {
|
|
|
1572
1656
|
}
|
|
1573
1657
|
};
|
|
1574
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
|
+
|
|
1575
1725
|
// src/toolSchemaResolver.ts
|
|
1576
1726
|
import { statSync } from "fs";
|
|
1577
1727
|
import { z } from "zod";
|
|
1578
1728
|
import { loadToolSchema, getToolSchemaPath } from "@faapi/faapi";
|
|
1579
|
-
async function resolveToolSchemaImpl(
|
|
1580
|
-
const schemaMod = await loadToolSchema(
|
|
1729
|
+
async function resolveToolSchemaImpl(ref, rootDir) {
|
|
1730
|
+
const schemaMod = await loadToolSchema(ref, rootDir);
|
|
1581
1731
|
if (!schemaMod) return void 0;
|
|
1582
1732
|
const schema = schemaMod.schema;
|
|
1583
1733
|
return {
|
|
@@ -1594,9 +1744,9 @@ async function resolveToolSchemaImpl(tool, rootDir) {
|
|
|
1594
1744
|
function createToolSchemaResolver(options) {
|
|
1595
1745
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
1596
1746
|
const schemaCache = /* @__PURE__ */ new Map();
|
|
1597
|
-
return (
|
|
1598
|
-
const zodPath = getToolSchemaPath(
|
|
1599
|
-
const key = `${zodPath}#${
|
|
1747
|
+
return (ref) => {
|
|
1748
|
+
const zodPath = getToolSchemaPath(ref, rootDir);
|
|
1749
|
+
const key = `${zodPath}#${ref.inputTypeName ?? ""}`;
|
|
1600
1750
|
let mtimeMs = -1;
|
|
1601
1751
|
try {
|
|
1602
1752
|
mtimeMs = statSync(zodPath).mtimeMs;
|
|
@@ -1606,7 +1756,7 @@ function createToolSchemaResolver(options) {
|
|
|
1606
1756
|
if (hit && hit.mtimeMs === mtimeMs) {
|
|
1607
1757
|
return hit.resolution;
|
|
1608
1758
|
}
|
|
1609
|
-
const resolution = resolveToolSchemaImpl(
|
|
1759
|
+
const resolution = resolveToolSchemaImpl(ref, rootDir);
|
|
1610
1760
|
schemaCache.set(key, { mtimeMs, resolution });
|
|
1611
1761
|
return resolution;
|
|
1612
1762
|
};
|
|
@@ -1641,6 +1791,7 @@ var agentPlugin = {
|
|
|
1641
1791
|
}
|
|
1642
1792
|
providers.set(name, createProvider(llmConfig));
|
|
1643
1793
|
}
|
|
1794
|
+
ctx.registries.llm.register(createLightComplete({ llms, providers }));
|
|
1644
1795
|
const runtimeConfig = {
|
|
1645
1796
|
maxTurns: agentConfig?.maxTurns,
|
|
1646
1797
|
maxAgentDepth: agentConfig?.maxAgentDepth,
|
|
@@ -1651,7 +1802,7 @@ var agentPlugin = {
|
|
|
1651
1802
|
filterTools: agentConfig?.filterTools
|
|
1652
1803
|
};
|
|
1653
1804
|
const rootDir = ctx.rootDir;
|
|
1654
|
-
const
|
|
1805
|
+
const resolveSchema = createToolSchemaResolver({ rootDir });
|
|
1655
1806
|
ctx.registries.agentHandle.register((ctx2) => {
|
|
1656
1807
|
return new Agent({
|
|
1657
1808
|
providers,
|
|
@@ -1670,8 +1821,9 @@ var agentPlugin = {
|
|
|
1670
1821
|
resolveSubAgents: registries.agent.resolveSubAgents,
|
|
1671
1822
|
// 加载器包装:注入 rootDir 用于 dev 按需编译模式
|
|
1672
1823
|
loadToolModule: (filePath, functionName) => loadToolModule(filePath, functionName, rootDir),
|
|
1673
|
-
//
|
|
1674
|
-
resolveToolSchema
|
|
1824
|
+
// schema 解析(zod.js → JSON Schema + safeParse 校验)——tool 与 agent 入参共用
|
|
1825
|
+
resolveToolSchema: resolveSchema,
|
|
1826
|
+
resolveAgentInputSchema: resolveSchema
|
|
1675
1827
|
});
|
|
1676
1828
|
});
|
|
1677
1829
|
console.log(
|
|
@@ -1687,7 +1839,9 @@ export {
|
|
|
1687
1839
|
AgentRecursionError,
|
|
1688
1840
|
AgentToolTimeoutError,
|
|
1689
1841
|
LLMProviderError,
|
|
1842
|
+
LLMTimeoutError,
|
|
1690
1843
|
ReactLoopError,
|
|
1844
|
+
createLightComplete,
|
|
1691
1845
|
createOpenAIProvider,
|
|
1692
1846
|
createProvider,
|
|
1693
1847
|
createToolSchemaResolver,
|