@faapi/agent 0.0.0-canary.0 → 3.1.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/LICENSE +21 -0
- package/README.md +85 -1
- package/dist/index.d.ts +688 -0
- package/dist/index.js +894 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -6
package/dist/index.js
ADDED
|
@@ -0,0 +1,894 @@
|
|
|
1
|
+
// src/providers/openai.ts
|
|
2
|
+
var DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
3
|
+
var RESERVED_CONFIG_KEYS = /* @__PURE__ */ new Set(["provider", "apiKey", "model", "baseURL", "models"]);
|
|
4
|
+
var LLMProviderError = class extends Error {
|
|
5
|
+
/** HTTP 状态码(网络错误 / JSON 解析错误为 undefined) */
|
|
6
|
+
status;
|
|
7
|
+
/** 响应体摘要(前 500 字符,便于诊断) */
|
|
8
|
+
body;
|
|
9
|
+
constructor(message, options) {
|
|
10
|
+
super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
11
|
+
this.name = "LLMProviderError";
|
|
12
|
+
this.status = options?.status;
|
|
13
|
+
this.body = options?.body;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
function createOpenAIProvider(config) {
|
|
17
|
+
const baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
18
|
+
const apiKey = config.apiKey;
|
|
19
|
+
function buildRequestBody(request) {
|
|
20
|
+
const modelName = request.model ?? Object.keys(config.models)[0];
|
|
21
|
+
const modelConfig = modelName ? config.models[modelName] : void 0;
|
|
22
|
+
const body = {
|
|
23
|
+
model: modelName,
|
|
24
|
+
messages: request.messages.map(toOpenAIMessage)
|
|
25
|
+
};
|
|
26
|
+
if (request.tools && request.tools.length > 0) {
|
|
27
|
+
body.tools = request.tools.map(toOpenAITool);
|
|
28
|
+
}
|
|
29
|
+
const mergedConfig = {};
|
|
30
|
+
for (const key of Object.keys(config)) {
|
|
31
|
+
if (RESERVED_CONFIG_KEYS.has(key)) continue;
|
|
32
|
+
const value = config[key];
|
|
33
|
+
if (value !== void 0) {
|
|
34
|
+
mergedConfig[key] = value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (modelConfig) {
|
|
38
|
+
for (const key of Object.keys(modelConfig)) {
|
|
39
|
+
if (RESERVED_CONFIG_KEYS.has(key)) continue;
|
|
40
|
+
const value = modelConfig[key];
|
|
41
|
+
if (value !== void 0) {
|
|
42
|
+
mergedConfig[key] = value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const key of Object.keys(mergedConfig)) {
|
|
47
|
+
const value = mergedConfig[key];
|
|
48
|
+
if (value !== void 0 && !(key in body)) {
|
|
49
|
+
body[key] = value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (request.temperature !== void 0) body.temperature = request.temperature;
|
|
53
|
+
if (request.maxTokens !== void 0) body.max_tokens = request.maxTokens;
|
|
54
|
+
return body;
|
|
55
|
+
}
|
|
56
|
+
function buildHeaders() {
|
|
57
|
+
const headers = { "Content-Type": "application/json" };
|
|
58
|
+
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
59
|
+
return headers;
|
|
60
|
+
}
|
|
61
|
+
async function safeFetch(url, init) {
|
|
62
|
+
try {
|
|
63
|
+
return await fetch(url, init);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
66
|
+
throw new LLMProviderError(`Network error: ${reason}`, { cause: err });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function ensureOk(response) {
|
|
70
|
+
if (response.ok) return "";
|
|
71
|
+
const bodyText = await response.text();
|
|
72
|
+
const excerpt = bodyText.slice(0, 500);
|
|
73
|
+
throw new LLMProviderError(`HTTP ${response.status}: ${excerpt}`, {
|
|
74
|
+
status: response.status,
|
|
75
|
+
body: excerpt
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async function complete(request) {
|
|
79
|
+
const url = `${baseURL}/chat/completions`;
|
|
80
|
+
const body = buildRequestBody(request);
|
|
81
|
+
const init = {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: buildHeaders(),
|
|
84
|
+
body: JSON.stringify(body)
|
|
85
|
+
};
|
|
86
|
+
const response = await safeFetch(url, init);
|
|
87
|
+
await ensureOk(response);
|
|
88
|
+
const bodyText = await response.text();
|
|
89
|
+
let json;
|
|
90
|
+
try {
|
|
91
|
+
json = JSON.parse(bodyText);
|
|
92
|
+
} catch {
|
|
93
|
+
const excerpt = bodyText.slice(0, 500);
|
|
94
|
+
throw new LLMProviderError(`Invalid JSON response: ${excerpt}`, {
|
|
95
|
+
status: response.status,
|
|
96
|
+
body: excerpt
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const choice = json.choices?.[0];
|
|
100
|
+
const msg = choice?.message;
|
|
101
|
+
if (!choice || !msg) {
|
|
102
|
+
const excerpt = bodyText.slice(0, 500);
|
|
103
|
+
throw new LLMProviderError("Empty choices in response", {
|
|
104
|
+
status: response.status,
|
|
105
|
+
body: excerpt
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const content = msg.content ?? "";
|
|
109
|
+
const toolCalls = parseToolCalls(msg.tool_calls, bodyText, response.status);
|
|
110
|
+
return {
|
|
111
|
+
message: {
|
|
112
|
+
role: "assistant",
|
|
113
|
+
content,
|
|
114
|
+
toolCalls
|
|
115
|
+
},
|
|
116
|
+
stopReason: mapStopReason(choice.finish_reason ?? void 0),
|
|
117
|
+
usage: mapUsage(json.usage)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async function* stream(request) {
|
|
121
|
+
const url = `${baseURL}/chat/completions`;
|
|
122
|
+
const body = buildRequestBody(request);
|
|
123
|
+
body.stream = true;
|
|
124
|
+
const init = {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: buildHeaders(),
|
|
127
|
+
body: JSON.stringify(body)
|
|
128
|
+
};
|
|
129
|
+
const response = await safeFetch(url, init);
|
|
130
|
+
await ensureOk(response);
|
|
131
|
+
if (!response.body) {
|
|
132
|
+
throw new LLMProviderError("Response body is null (streaming unsupported)", {
|
|
133
|
+
status: response.status
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const reader = response.body.getReader();
|
|
137
|
+
const decoder = new TextDecoder();
|
|
138
|
+
let buffer = "";
|
|
139
|
+
const accumulators = /* @__PURE__ */ new Map();
|
|
140
|
+
let finishReason;
|
|
141
|
+
let usage;
|
|
142
|
+
try {
|
|
143
|
+
while (true) {
|
|
144
|
+
const { done, value } = await reader.read();
|
|
145
|
+
if (done) break;
|
|
146
|
+
buffer += decoder.decode(value, { stream: true });
|
|
147
|
+
let sep;
|
|
148
|
+
while ((sep = buffer.indexOf("\n\n")) >= 0) {
|
|
149
|
+
const eventStr = buffer.slice(0, sep);
|
|
150
|
+
buffer = buffer.slice(sep + 2);
|
|
151
|
+
const data = extractSSEData(eventStr);
|
|
152
|
+
if (data === null) continue;
|
|
153
|
+
if (data === "[DONE]") {
|
|
154
|
+
yield finalizeStreamChunk(accumulators, finishReason, usage);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
let chunk;
|
|
158
|
+
try {
|
|
159
|
+
chunk = JSON.parse(data);
|
|
160
|
+
} catch {
|
|
161
|
+
const excerpt = data.slice(0, 500);
|
|
162
|
+
throw new LLMProviderError(`Invalid SSE chunk: ${excerpt}`, {
|
|
163
|
+
status: response.status,
|
|
164
|
+
body: excerpt
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
168
|
+
if (delta) {
|
|
169
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
170
|
+
yield { deltaContent: delta.content };
|
|
171
|
+
}
|
|
172
|
+
if (delta.tool_calls) {
|
|
173
|
+
for (const tc of delta.tool_calls) {
|
|
174
|
+
accumulateToolCall(accumulators, tc);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const fr = chunk.choices?.[0]?.finish_reason;
|
|
179
|
+
if (fr) finishReason = mapStopReason(fr);
|
|
180
|
+
if (chunk.usage) usage = mapUsage(chunk.usage);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
yield finalizeStreamChunk(accumulators, finishReason, usage);
|
|
184
|
+
} finally {
|
|
185
|
+
reader.releaseLock();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return { complete, stream };
|
|
189
|
+
}
|
|
190
|
+
function toOpenAIMessage(msg) {
|
|
191
|
+
const out = {
|
|
192
|
+
role: msg.role,
|
|
193
|
+
content: msg.content
|
|
194
|
+
};
|
|
195
|
+
if (msg.toolCallId !== void 0) out.tool_call_id = msg.toolCallId;
|
|
196
|
+
if (msg.toolCalls !== void 0 && msg.toolCalls.length > 0) {
|
|
197
|
+
out.tool_calls = msg.toolCalls.map((tc) => ({
|
|
198
|
+
id: tc.id,
|
|
199
|
+
type: "function",
|
|
200
|
+
function: { name: tc.name, arguments: JSON.stringify(tc.arguments) }
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
205
|
+
function toOpenAITool(tool) {
|
|
206
|
+
return {
|
|
207
|
+
type: "function",
|
|
208
|
+
function: {
|
|
209
|
+
name: tool.name,
|
|
210
|
+
description: tool.description,
|
|
211
|
+
parameters: tool.input
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function parseToolCalls(toolCalls, bodyText, status) {
|
|
216
|
+
if (!toolCalls || toolCalls.length === 0) return void 0;
|
|
217
|
+
const result = [];
|
|
218
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
219
|
+
const tc = toolCalls[i];
|
|
220
|
+
const argsStr = tc?.function?.arguments ?? "{}";
|
|
221
|
+
let args;
|
|
222
|
+
try {
|
|
223
|
+
args = JSON.parse(argsStr);
|
|
224
|
+
} catch {
|
|
225
|
+
const excerpt = argsStr.slice(0, 500);
|
|
226
|
+
throw new LLMProviderError(`Invalid tool arguments JSON: ${excerpt}`, {
|
|
227
|
+
status,
|
|
228
|
+
body: bodyText.slice(0, 500)
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
result.push({
|
|
232
|
+
id: tc?.id ?? `call_${i}`,
|
|
233
|
+
name: tc?.function?.name ?? "",
|
|
234
|
+
arguments: args
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
function mapStopReason(fr) {
|
|
240
|
+
switch (fr) {
|
|
241
|
+
case "stop":
|
|
242
|
+
return "stop";
|
|
243
|
+
case "tool_calls":
|
|
244
|
+
return "tool_calls";
|
|
245
|
+
case "length":
|
|
246
|
+
return "length";
|
|
247
|
+
case "content_filter":
|
|
248
|
+
return "content_filter";
|
|
249
|
+
default:
|
|
250
|
+
return "other";
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function mapUsage(u) {
|
|
254
|
+
if (!u) return void 0;
|
|
255
|
+
return {
|
|
256
|
+
promptTokens: u.prompt_tokens ?? 0,
|
|
257
|
+
completionTokens: u.completion_tokens ?? 0,
|
|
258
|
+
totalTokens: u.total_tokens ?? 0
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function extractSSEData(event) {
|
|
262
|
+
const dataLines = [];
|
|
263
|
+
for (const line of event.split("\n")) {
|
|
264
|
+
if (line === "" || line.startsWith(":")) continue;
|
|
265
|
+
if (line.startsWith("data:")) {
|
|
266
|
+
dataLines.push(line.slice(5).replace(/^ /, ""));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (dataLines.length === 0) return null;
|
|
270
|
+
return dataLines.join("\n");
|
|
271
|
+
}
|
|
272
|
+
function accumulateToolCall(accumulators, tc) {
|
|
273
|
+
const idx = tc.index ?? 0;
|
|
274
|
+
const acc = accumulators.get(idx) ?? { argsString: "" };
|
|
275
|
+
if (tc.id) acc.id = tc.id;
|
|
276
|
+
if (tc.function?.name) acc.name = tc.function.name;
|
|
277
|
+
if (tc.function?.arguments) acc.argsString += tc.function.arguments;
|
|
278
|
+
accumulators.set(idx, acc);
|
|
279
|
+
}
|
|
280
|
+
function finalizeStreamChunk(accumulators, finishReason, usage) {
|
|
281
|
+
const toolCalls = [];
|
|
282
|
+
if (accumulators.size > 0) {
|
|
283
|
+
const indices = Array.from(accumulators.keys()).sort((a, b) => a - b);
|
|
284
|
+
for (const idx of indices) {
|
|
285
|
+
const acc = accumulators.get(idx);
|
|
286
|
+
if (!acc.id || !acc.name) continue;
|
|
287
|
+
let args;
|
|
288
|
+
try {
|
|
289
|
+
args = acc.argsString ? JSON.parse(acc.argsString) : {};
|
|
290
|
+
} catch {
|
|
291
|
+
const excerpt = acc.argsString.slice(0, 500);
|
|
292
|
+
throw new LLMProviderError(`Invalid tool arguments JSON: ${excerpt}`, {
|
|
293
|
+
body: excerpt
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
toolCalls.push({
|
|
297
|
+
id: acc.id,
|
|
298
|
+
name: acc.name,
|
|
299
|
+
arguments: args
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const chunk = {};
|
|
304
|
+
if (toolCalls.length > 0) chunk.toolCalls = toolCalls;
|
|
305
|
+
if (finishReason) chunk.finishReason = finishReason;
|
|
306
|
+
if (usage) chunk.usage = usage;
|
|
307
|
+
return chunk;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/provider.ts
|
|
311
|
+
function createProvider(config) {
|
|
312
|
+
switch (config.provider) {
|
|
313
|
+
case "openai":
|
|
314
|
+
return createOpenAIProvider(config);
|
|
315
|
+
default:
|
|
316
|
+
throw new Error(`Unsupported LLM provider: ${String(config.provider)}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/reactLoop.ts
|
|
321
|
+
var ReactLoopError = class extends Error {
|
|
322
|
+
/** 配置的 maxTurns 值 */
|
|
323
|
+
maxTurns;
|
|
324
|
+
constructor(message, maxTurns) {
|
|
325
|
+
super(message);
|
|
326
|
+
this.name = "ReactLoopError";
|
|
327
|
+
this.maxTurns = maxTurns;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
var DEFAULT_MAX_TURNS = 10;
|
|
331
|
+
function stringifyResult(result) {
|
|
332
|
+
if (typeof result === "string") return result;
|
|
333
|
+
if (result === void 0) return "";
|
|
334
|
+
return JSON.stringify(result);
|
|
335
|
+
}
|
|
336
|
+
function stringifyError(err) {
|
|
337
|
+
if (err instanceof Error) return err.message;
|
|
338
|
+
return String(err);
|
|
339
|
+
}
|
|
340
|
+
function accumulateUsage(a, b) {
|
|
341
|
+
if (!a) return { ...b };
|
|
342
|
+
return {
|
|
343
|
+
promptTokens: a.promptTokens + b.promptTokens,
|
|
344
|
+
completionTokens: a.completionTokens + b.completionTokens,
|
|
345
|
+
totalTokens: a.totalTokens + b.totalTokens
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function buildInitialMessages(input, systemPrompt) {
|
|
349
|
+
const messages = [];
|
|
350
|
+
if (systemPrompt) {
|
|
351
|
+
messages.push({ role: "system", content: systemPrompt });
|
|
352
|
+
}
|
|
353
|
+
messages.push({ role: "user", content: input });
|
|
354
|
+
return messages;
|
|
355
|
+
}
|
|
356
|
+
function buildRequestExtras(config) {
|
|
357
|
+
return {
|
|
358
|
+
tools: config.tools,
|
|
359
|
+
model: config.model,
|
|
360
|
+
temperature: config.temperature,
|
|
361
|
+
maxTokens: config.maxTokens
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
async function reactLoop(input, config) {
|
|
365
|
+
const messages = buildInitialMessages(input, config.systemPrompt);
|
|
366
|
+
const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
367
|
+
const extras = buildRequestExtras(config);
|
|
368
|
+
let totalUsage;
|
|
369
|
+
let turns = 0;
|
|
370
|
+
while (turns < maxTurns) {
|
|
371
|
+
turns++;
|
|
372
|
+
const response = await config.provider.complete({
|
|
373
|
+
messages: [...messages],
|
|
374
|
+
...extras
|
|
375
|
+
});
|
|
376
|
+
if (response.usage) {
|
|
377
|
+
totalUsage = accumulateUsage(totalUsage, response.usage);
|
|
378
|
+
}
|
|
379
|
+
messages.push(response.message);
|
|
380
|
+
if (response.stopReason !== "tool_calls" || !response.message.toolCalls) {
|
|
381
|
+
return {
|
|
382
|
+
content: response.message.content,
|
|
383
|
+
messages,
|
|
384
|
+
turns,
|
|
385
|
+
stopReason: response.stopReason,
|
|
386
|
+
usage: totalUsage
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
for (const toolCall of response.message.toolCalls) {
|
|
390
|
+
let resultStr;
|
|
391
|
+
try {
|
|
392
|
+
const result = await config.executeTool(toolCall.name, toolCall.arguments);
|
|
393
|
+
resultStr = stringifyResult(result);
|
|
394
|
+
} catch (err) {
|
|
395
|
+
resultStr = stringifyError(err);
|
|
396
|
+
}
|
|
397
|
+
messages.push({
|
|
398
|
+
role: "tool",
|
|
399
|
+
content: resultStr,
|
|
400
|
+
toolCallId: toolCall.id
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
throw new ReactLoopError(
|
|
405
|
+
`Max turns (${maxTurns}) exceeded \u2014 agent did not converge to a final answer`,
|
|
406
|
+
maxTurns
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
async function* reactLoopStream(input, config) {
|
|
410
|
+
const messages = buildInitialMessages(input, config.systemPrompt);
|
|
411
|
+
const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
412
|
+
const extras = buildRequestExtras(config);
|
|
413
|
+
let totalUsage;
|
|
414
|
+
let turns = 0;
|
|
415
|
+
while (turns < maxTurns) {
|
|
416
|
+
turns++;
|
|
417
|
+
let turnContent = "";
|
|
418
|
+
let toolCalls;
|
|
419
|
+
let finishReason;
|
|
420
|
+
for await (const chunk of config.provider.stream({
|
|
421
|
+
messages: [...messages],
|
|
422
|
+
...extras
|
|
423
|
+
})) {
|
|
424
|
+
if (typeof chunk.deltaContent === "string" && chunk.deltaContent.length > 0) {
|
|
425
|
+
turnContent += chunk.deltaContent;
|
|
426
|
+
yield { deltaContent: chunk.deltaContent };
|
|
427
|
+
}
|
|
428
|
+
if (chunk.toolCalls && chunk.toolCalls.length > 0) {
|
|
429
|
+
toolCalls = chunk.toolCalls;
|
|
430
|
+
}
|
|
431
|
+
if (chunk.finishReason) {
|
|
432
|
+
finishReason = chunk.finishReason;
|
|
433
|
+
}
|
|
434
|
+
if (chunk.usage) {
|
|
435
|
+
totalUsage = accumulateUsage(totalUsage, chunk.usage);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const assistantMessage = {
|
|
439
|
+
role: "assistant",
|
|
440
|
+
content: turnContent
|
|
441
|
+
};
|
|
442
|
+
if (toolCalls) {
|
|
443
|
+
assistantMessage.toolCalls = toolCalls;
|
|
444
|
+
}
|
|
445
|
+
messages.push(assistantMessage);
|
|
446
|
+
if (finishReason !== "tool_calls" || !toolCalls) {
|
|
447
|
+
yield {
|
|
448
|
+
done: {
|
|
449
|
+
content: turnContent,
|
|
450
|
+
turns,
|
|
451
|
+
stopReason: finishReason ?? "other",
|
|
452
|
+
usage: totalUsage
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
for (const toolCall of toolCalls) {
|
|
458
|
+
yield { toolCall: { name: toolCall.name, arguments: toolCall.arguments } };
|
|
459
|
+
let resultStr;
|
|
460
|
+
try {
|
|
461
|
+
const result = await config.executeTool(toolCall.name, toolCall.arguments);
|
|
462
|
+
resultStr = stringifyResult(result);
|
|
463
|
+
} catch (err) {
|
|
464
|
+
resultStr = stringifyError(err);
|
|
465
|
+
}
|
|
466
|
+
yield { toolResult: { name: toolCall.name, result: resultStr } };
|
|
467
|
+
messages.push({
|
|
468
|
+
role: "tool",
|
|
469
|
+
content: resultStr,
|
|
470
|
+
toolCallId: toolCall.id
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
throw new ReactLoopError(
|
|
475
|
+
`Max turns (${maxTurns}) exceeded \u2014 agent did not converge to a final answer`,
|
|
476
|
+
maxTurns
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// src/agent.ts
|
|
481
|
+
var DEFAULT_MAX_AGENT_DEPTH = 3;
|
|
482
|
+
var AgentError = class extends Error {
|
|
483
|
+
constructor(message) {
|
|
484
|
+
super(message);
|
|
485
|
+
this.name = "AgentError";
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
var AgentRecursionError = class extends AgentError {
|
|
489
|
+
/** 配置的 maxAgentDepth 值 */
|
|
490
|
+
maxDepth;
|
|
491
|
+
/** 当前递归深度(超出 maxDepth) */
|
|
492
|
+
currentDepth;
|
|
493
|
+
constructor(maxDepth, currentDepth) {
|
|
494
|
+
super(
|
|
495
|
+
`Agent recursion depth exceeded: current depth ${currentDepth} > maxAgentDepth ${maxDepth}`
|
|
496
|
+
);
|
|
497
|
+
this.name = "AgentRecursionError";
|
|
498
|
+
this.maxDepth = maxDepth;
|
|
499
|
+
this.currentDepth = currentDepth;
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
var Agent = class _Agent {
|
|
503
|
+
deps;
|
|
504
|
+
/** 当前递归深度(根 agent 为 1,sub-agent 递增) */
|
|
505
|
+
depth;
|
|
506
|
+
/**
|
|
507
|
+
* tool schema 解析缓存(按 tool.name 缓存,含 undefined 结果)
|
|
508
|
+
*
|
|
509
|
+
* `buildToolDefinitions` 组装 LLM tool 列表时解析一次 schema(取 jsonSchema),
|
|
510
|
+
* `executeTool` 执行前校验时复用同一份 schema(取 validate)——
|
|
511
|
+
* 避免每次 tool 执行都重新 `loadToolSchema` + `z.toJSONSchema`。
|
|
512
|
+
*
|
|
513
|
+
* 实例级缓存:sub-agent 各有独立 cache(tool 集合可能不同)。
|
|
514
|
+
*/
|
|
515
|
+
schemaCache = /* @__PURE__ */ new Map();
|
|
516
|
+
/**
|
|
517
|
+
* @param deps 运行时依赖(访问器 + providers Map + defaultProvider + llms + config)
|
|
518
|
+
* @param depth 递归深度(默认 1 = 根 agent;sub-agent 递归时传入 depth+1)
|
|
519
|
+
*/
|
|
520
|
+
constructor(deps, depth = 1) {
|
|
521
|
+
this.deps = deps;
|
|
522
|
+
this.depth = depth;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* 非流式执行——组装 config 调 [reactLoop](./reactLoop.md)
|
|
526
|
+
*
|
|
527
|
+
* @param input 用户输入
|
|
528
|
+
* @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens
|
|
529
|
+
* (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))
|
|
530
|
+
* @returns 最终结果(content + messages + turns + stopReason + usage)
|
|
531
|
+
* @throws {AgentError} agent 未注册
|
|
532
|
+
* @throws {ReactLoopError} 超出 maxTurns
|
|
533
|
+
* @throws {Error} provider.complete 抛错时立即传播
|
|
534
|
+
*/
|
|
535
|
+
async run(input, options) {
|
|
536
|
+
const config = await this.buildLoopConfig(options);
|
|
537
|
+
return reactLoop(input, config);
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* 流式执行——组装 config 调 [reactLoopStream](./reactLoop.md)
|
|
541
|
+
*
|
|
542
|
+
* @param input 用户输入
|
|
543
|
+
* @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens
|
|
544
|
+
* (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))
|
|
545
|
+
* @yields 流式 chunk(deltaContent / toolCall / toolResult / done)
|
|
546
|
+
* @throws {AgentError} agent 未注册
|
|
547
|
+
* @throws {ReactLoopError} 超出 maxTurns
|
|
548
|
+
* @throws {Error} provider.stream 抛错时立即传播
|
|
549
|
+
*/
|
|
550
|
+
async *stream(input, options) {
|
|
551
|
+
const config = await this.buildLoopConfig(options);
|
|
552
|
+
yield* reactLoopStream(input, config);
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* 把自身包装为 `AgentToolDescriptor` 供 LLM 当 tool 调用
|
|
556
|
+
*
|
|
557
|
+
* 与 [agentRegistry.asTool](../../faapi/src/injection/agentRegistry.md) 同构——
|
|
558
|
+
* Agent 类自带此方法便于在注入器场景直接调用(不必再过注册表)。
|
|
559
|
+
*
|
|
560
|
+
* @returns `AgentToolDescriptor` 或 `undefined`(agent 未注册)
|
|
561
|
+
*/
|
|
562
|
+
asTool() {
|
|
563
|
+
const meta = this.deps.getAgent(this.deps.agentName);
|
|
564
|
+
if (!meta) return void 0;
|
|
565
|
+
return {
|
|
566
|
+
kind: "agent",
|
|
567
|
+
name: `agent.${meta.name}`,
|
|
568
|
+
agentName: meta.name,
|
|
569
|
+
description: meta.description,
|
|
570
|
+
metadata: meta
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
// ─── 内部方法 ────────────────────────────────────────
|
|
574
|
+
/**
|
|
575
|
+
* 查询 tool schema(带缓存)
|
|
576
|
+
*
|
|
577
|
+
* `buildToolDefinitions` 与 `executeTool` 共用此方法——
|
|
578
|
+
* 首次调用触发 `deps.resolveToolSchema`(加载 zod.js + 生成 JSON Schema),
|
|
579
|
+
* 后续命中缓存直接返回(含 `undefined` 结果,用 `has` 区分未解析 vs 解析为空)。
|
|
580
|
+
*
|
|
581
|
+
* `deps.resolveToolSchema` 未提供时直接返回 `undefined`,不写缓存。
|
|
582
|
+
*/
|
|
583
|
+
async getToolSchema(tool) {
|
|
584
|
+
if (!this.deps.resolveToolSchema) return void 0;
|
|
585
|
+
if (this.schemaCache.has(tool.name)) {
|
|
586
|
+
return this.schemaCache.get(tool.name);
|
|
587
|
+
}
|
|
588
|
+
const resolved = await this.deps.resolveToolSchema(tool);
|
|
589
|
+
this.schemaCache.set(tool.name, resolved);
|
|
590
|
+
return resolved;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* 组装 ReactLoopConfig
|
|
594
|
+
*
|
|
595
|
+
* 1. 查 agent 元数据(未注册抛 AgentError)——用 `getAgent` 拿 AgentCore
|
|
596
|
+
* (LLM-facing 字段:systemPrompt / model / maxTurns)
|
|
597
|
+
* 2. buildToolDefinitions 组装 tool 列表
|
|
598
|
+
* 3. config 字段优先级(高 → 低):`options` > agent 元数据 > 全局 AgentRuntimeConfig / deps.defaultProvider
|
|
599
|
+
*
|
|
600
|
+
* `options.model` 是字符串 key,由 {@link resolveModelKey} 解析为 provider + model
|
|
601
|
+
* (支持 llms key 精确匹配 / `provider/model` 一体化 / 纯 model 名模糊匹配)。
|
|
602
|
+
* 不传 `options.model` 时用 `deps.defaultProvider` + agent 元数据 `config.model`。
|
|
603
|
+
* 详见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」。
|
|
604
|
+
*/
|
|
605
|
+
async buildLoopConfig(options) {
|
|
606
|
+
const meta = this.deps.getAgent(this.deps.agentName);
|
|
607
|
+
if (!meta) {
|
|
608
|
+
throw new AgentError(`Agent "${this.deps.agentName}" is not registered`);
|
|
609
|
+
}
|
|
610
|
+
const tools = await this.buildToolDefinitions();
|
|
611
|
+
const { provider, model } = this.resolveModelKey(options?.model, meta);
|
|
612
|
+
return {
|
|
613
|
+
provider,
|
|
614
|
+
systemPrompt: meta.systemPrompt,
|
|
615
|
+
model,
|
|
616
|
+
temperature: options?.temperature,
|
|
617
|
+
maxTokens: options?.maxTokens,
|
|
618
|
+
maxTurns: meta.maxTurns ?? this.deps.config?.maxTurns,
|
|
619
|
+
tools,
|
|
620
|
+
executeTool: async (name, args) => this.executeTool(name, args)
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* 解析 `options.model` 字符串 key → provider + model
|
|
625
|
+
*
|
|
626
|
+
* 规则见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」:
|
|
627
|
+
* 1. `undefined` → `deps.defaultProvider` + `meta.model`
|
|
628
|
+
* 2. 精确匹配 `deps.providers` 的 key → 该 provider + 其 `models` 第一个 key
|
|
629
|
+
* 3. 含 `/` → `provider/model` 形式,`deps.providers.get(provider)` + 该 model
|
|
630
|
+
* (要求该 model 在 `deps.llms[provider].models` 里)
|
|
631
|
+
* 4. 不含 `/` 且非 provider key → 在所有 provider 的 `models` 里按 model 名查找
|
|
632
|
+
* - 唯一 → 该 provider + 该 model
|
|
633
|
+
* - 多个 → 抛 `AgentError`(要求用 `provider/model` 消歧)
|
|
634
|
+
* - 无 → 抛 `AgentError`
|
|
635
|
+
*
|
|
636
|
+
* @throws {AgentError} key 解析失败(provider/model 不存在或歧义)
|
|
637
|
+
*/
|
|
638
|
+
resolveModelKey(key, meta) {
|
|
639
|
+
if (key === void 0) {
|
|
640
|
+
return { provider: this.deps.defaultProvider, model: meta.model };
|
|
641
|
+
}
|
|
642
|
+
const byProviderKey = this.deps.providers.get(key);
|
|
643
|
+
if (byProviderKey) {
|
|
644
|
+
const llmConfig = this.deps.llms[key];
|
|
645
|
+
const firstModel = llmConfig ? Object.keys(llmConfig.models)[0] : void 0;
|
|
646
|
+
return { provider: byProviderKey, model: firstModel ?? meta.model };
|
|
647
|
+
}
|
|
648
|
+
if (key.includes("/")) {
|
|
649
|
+
const slashIdx = key.indexOf("/");
|
|
650
|
+
const providerName = key.slice(0, slashIdx);
|
|
651
|
+
const modelName = key.slice(slashIdx + 1);
|
|
652
|
+
const provider = this.deps.providers.get(providerName);
|
|
653
|
+
if (!provider) {
|
|
654
|
+
throw new AgentError(`Unknown provider "${providerName}" in model key "${key}"`);
|
|
655
|
+
}
|
|
656
|
+
const llmConfig = this.deps.llms[providerName];
|
|
657
|
+
if (!llmConfig || !llmConfig.models[modelName]) {
|
|
658
|
+
throw new AgentError(
|
|
659
|
+
`Model "${modelName}" not found in provider "${providerName}". Declare it in config.agent.llms.${providerName}.models.`
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
return { provider, model: modelName };
|
|
663
|
+
}
|
|
664
|
+
const matches = [];
|
|
665
|
+
for (const [providerName, provider] of this.deps.providers) {
|
|
666
|
+
const llmConfig = this.deps.llms[providerName];
|
|
667
|
+
if (llmConfig && llmConfig.models[key]) {
|
|
668
|
+
matches.push({ provider, providerName });
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (matches.length === 1) {
|
|
672
|
+
return { provider: matches[0].provider, model: key };
|
|
673
|
+
}
|
|
674
|
+
if (matches.length > 1) {
|
|
675
|
+
throw new AgentError(
|
|
676
|
+
`Model "${key}" is ambiguous (found in providers: ${matches.map((m) => m.providerName).join(", ")}). Use "provider/model" to disambiguate.`
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
throw new AgentError(
|
|
680
|
+
`Model "${key}" not found in any provider. Declare it in config.agent.llms.*.models.`
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* 组装 LLM 可见 tool 列表
|
|
685
|
+
*
|
|
686
|
+
* 合并两个来源(按 `name` 去重,先入者保留):
|
|
687
|
+
* 1. **resolveAgentTools** —— agent 显式声明的 `tools` 引用
|
|
688
|
+
* 2. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`
|
|
689
|
+
*
|
|
690
|
+
* 每个常规 tool 的 `input`:
|
|
691
|
+
* - `resolveToolSchema` 提供 → 用其 `jsonSchema`
|
|
692
|
+
* - 未提供 / tool 无 `inputTypeName` → 自由 schema `{ type: 'object' }`
|
|
693
|
+
*
|
|
694
|
+
* sub-agent 的 `input` 始终为 `{ type: 'object' }`(agent 参数开放)。
|
|
695
|
+
*/
|
|
696
|
+
async buildToolDefinitions() {
|
|
697
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
698
|
+
for (const tool of this.deps.resolveAgentTools(this.deps.agentName)) {
|
|
699
|
+
if (definitions.has(tool.name)) continue;
|
|
700
|
+
const schemaRes = await this.getToolSchema(tool);
|
|
701
|
+
definitions.set(tool.name, {
|
|
702
|
+
name: tool.name,
|
|
703
|
+
description: tool.description,
|
|
704
|
+
input: schemaRes?.jsonSchema ?? { type: "object" }
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
for (const subAgent of this.deps.resolveSubAgents(this.deps.agentName)) {
|
|
708
|
+
const name = `agent.${subAgent.name}`;
|
|
709
|
+
if (definitions.has(name)) continue;
|
|
710
|
+
definitions.set(name, {
|
|
711
|
+
name,
|
|
712
|
+
description: subAgent.description,
|
|
713
|
+
input: { type: "object" }
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
return Array.from(definitions.values());
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* tool 执行路由(由 reactLoop 调用)
|
|
720
|
+
*
|
|
721
|
+
* - `agent.` 前缀 → {@link executeSubAgent} 递归
|
|
722
|
+
* - 常规 tool → `loadToolModule` 加载 handler + 可选 input 校验 → 调用
|
|
723
|
+
*
|
|
724
|
+
* **常规 tool 校验失败**:不抛错,返回 `{ error }` 对象——reactLoop stringify 后
|
|
725
|
+
* 作为 tool 结果回传 LLM,LLM 可据此修正参数重试。
|
|
726
|
+
*
|
|
727
|
+
* **tool 未找到 / 加载失败**:抛错,被 reactLoop catch 后同样回传 LLM。
|
|
728
|
+
*/
|
|
729
|
+
async executeTool(name, args) {
|
|
730
|
+
if (name.startsWith("agent.")) {
|
|
731
|
+
return this.executeSubAgent(name.slice(6), args);
|
|
732
|
+
}
|
|
733
|
+
const tool = this.deps.getTool(name);
|
|
734
|
+
if (!tool) {
|
|
735
|
+
throw new Error(`Tool "${name}" not found`);
|
|
736
|
+
}
|
|
737
|
+
const schemaRes = await this.getToolSchema(tool);
|
|
738
|
+
let callArgs = args;
|
|
739
|
+
if (schemaRes) {
|
|
740
|
+
const result = schemaRes.validate(args);
|
|
741
|
+
if (!result.ok) {
|
|
742
|
+
return { error: result.error };
|
|
743
|
+
}
|
|
744
|
+
callArgs = result.value ?? args;
|
|
745
|
+
}
|
|
746
|
+
const mod = await this.deps.loadToolModule(tool.filePath, tool.functionName);
|
|
747
|
+
return await mod.handler(callArgs);
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* sub-agent 递归执行
|
|
751
|
+
*
|
|
752
|
+
* 1. `maxAgentDepth` 防护——超限抛 {@link AgentRecursionError}
|
|
753
|
+
* 2. sub-agent handler 导出 `run` 时调自定义 `mod.run(args)`
|
|
754
|
+
* 3. 无 `run` 时调 `subAgent.run(JSON.stringify(args))` 走默认 reactLoop
|
|
755
|
+
*
|
|
756
|
+
* 自定义 run 接收原始 args 对象;默认 reactLoop 接收 stringify 后的 args
|
|
757
|
+
* 作为 user 消息(agent-as-tool input 为开放式 JSON)。
|
|
758
|
+
*
|
|
759
|
+
* 加载 handler.js 用 `getAgentEntry`(返回 AgentMetadata,含 filePath/hasRun),
|
|
760
|
+
* 而非 `getAgent`(返回 AgentCore,无代码加载细节)。DB skill 无文件,
|
|
761
|
+
* `getAgentEntry` 返回 `undefined`,走默认 reactLoop。
|
|
762
|
+
*/
|
|
763
|
+
async executeSubAgent(subName, args) {
|
|
764
|
+
const newDepth = this.depth + 1;
|
|
765
|
+
const maxDepth = this.deps.config?.maxAgentDepth ?? DEFAULT_MAX_AGENT_DEPTH;
|
|
766
|
+
if (newDepth > maxDepth) {
|
|
767
|
+
throw new AgentRecursionError(maxDepth, newDepth);
|
|
768
|
+
}
|
|
769
|
+
const subDeps = { ...this.deps, agentName: subName };
|
|
770
|
+
const subAgent = new _Agent(subDeps, newDepth);
|
|
771
|
+
const entry = this.deps.getAgentEntry(subName);
|
|
772
|
+
if (entry?.hasRun) {
|
|
773
|
+
const mod = await this.deps.loadAgentModule(entry.filePath, entry.hasRun);
|
|
774
|
+
if (mod.run) {
|
|
775
|
+
return await mod.run(args);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
const result = await subAgent.run(typeof args === "string" ? args : JSON.stringify(args));
|
|
779
|
+
return result.content;
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
// src/plugin.ts
|
|
784
|
+
import {
|
|
785
|
+
registerAgentHandleFactory,
|
|
786
|
+
getAgent,
|
|
787
|
+
getAgentEntry,
|
|
788
|
+
getTool,
|
|
789
|
+
resolveAgentTools,
|
|
790
|
+
resolveSubAgents,
|
|
791
|
+
loadAgentModule,
|
|
792
|
+
loadToolModule,
|
|
793
|
+
loadToolSchema
|
|
794
|
+
} from "@faapi/faapi";
|
|
795
|
+
import { z } from "zod";
|
|
796
|
+
async function resolveToolSchemaImpl(tool, rootDir) {
|
|
797
|
+
const schemaMod = await loadToolSchema(tool, rootDir);
|
|
798
|
+
if (!schemaMod) return void 0;
|
|
799
|
+
const schema = schemaMod.schema;
|
|
800
|
+
return {
|
|
801
|
+
jsonSchema: z.toJSONSchema(schema),
|
|
802
|
+
validate: (input) => {
|
|
803
|
+
const result = schema.safeParse(input);
|
|
804
|
+
if (result.success) {
|
|
805
|
+
return { ok: true, value: result.data };
|
|
806
|
+
}
|
|
807
|
+
return { ok: false, error: result.error.message };
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
function readAgentConfig(ctx) {
|
|
812
|
+
const raw = ctx.config?.agent;
|
|
813
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
814
|
+
return raw;
|
|
815
|
+
}
|
|
816
|
+
var agentPlugin = {
|
|
817
|
+
name: "@faapi/agent",
|
|
818
|
+
setup(ctx) {
|
|
819
|
+
const agentConfig = readAgentConfig(ctx);
|
|
820
|
+
if (!agentConfig?.llms) {
|
|
821
|
+
console.warn(
|
|
822
|
+
"! @faapi/agent: config.agent.llms not configured, agent parameter injection disabled"
|
|
823
|
+
);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
if (!agentConfig.defaultAgent) {
|
|
827
|
+
console.warn(
|
|
828
|
+
"! @faapi/agent: config.agent.defaultAgent not configured, agent parameter injection disabled"
|
|
829
|
+
);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
const llms = agentConfig.llms;
|
|
833
|
+
const providers = /* @__PURE__ */ new Map();
|
|
834
|
+
for (const [name, llmConfig] of Object.entries(llms)) {
|
|
835
|
+
providers.set(name, createProvider(llmConfig));
|
|
836
|
+
}
|
|
837
|
+
const defaultLlm = agentConfig.defaultLlm ?? Object.keys(llms)[0];
|
|
838
|
+
const defaultProvider = providers.get(defaultLlm);
|
|
839
|
+
if (!defaultProvider) {
|
|
840
|
+
console.warn(
|
|
841
|
+
`! @faapi/agent: config.agent.defaultLlm "${defaultLlm}" not found in llms, agent parameter injection disabled`
|
|
842
|
+
);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
const runtimeConfig = {
|
|
846
|
+
maxTurns: agentConfig.maxTurns,
|
|
847
|
+
maxAgentDepth: agentConfig.maxAgentDepth
|
|
848
|
+
};
|
|
849
|
+
const rootDir = ctx.rootDir;
|
|
850
|
+
const defaultAgent = agentConfig.defaultAgent;
|
|
851
|
+
const resolveToolSchema = (tool) => resolveToolSchemaImpl(tool, rootDir);
|
|
852
|
+
registerAgentHandleFactory(() => {
|
|
853
|
+
return new Agent({
|
|
854
|
+
providers,
|
|
855
|
+
defaultProvider,
|
|
856
|
+
llms,
|
|
857
|
+
defaultLlm,
|
|
858
|
+
agentName: defaultAgent,
|
|
859
|
+
rootDir,
|
|
860
|
+
config: runtimeConfig,
|
|
861
|
+
// 注册表/加载器访问器——从 @faapi/faapi import 的单例模块
|
|
862
|
+
// createAppBase 启动时已水合 agentRegistry / toolRegistry
|
|
863
|
+
// getAgent 返回 AgentCore(LLM-facing);getAgentEntry 返回 AgentMetadata(含 filePath/hasRun,供加载 handler.js)
|
|
864
|
+
getAgent,
|
|
865
|
+
getAgentEntry,
|
|
866
|
+
getTool,
|
|
867
|
+
resolveAgentTools,
|
|
868
|
+
resolveSubAgents,
|
|
869
|
+
// 加载器包装:注入 rootDir 用于 dev 按需编译模式
|
|
870
|
+
loadToolModule: (filePath, functionName) => loadToolModule(filePath, functionName, rootDir),
|
|
871
|
+
loadAgentModule: (filePath, hasRun) => loadAgentModule(filePath, hasRun, rootDir),
|
|
872
|
+
// tool schema 解析(zod.js → JSON Schema + safeParse 校验)
|
|
873
|
+
resolveToolSchema
|
|
874
|
+
});
|
|
875
|
+
});
|
|
876
|
+
console.log(
|
|
877
|
+
`- @faapi/agent: default agent "${defaultAgent}" (provider: ${defaultLlm}) available via agent parameter injection`
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
var plugin_default = agentPlugin;
|
|
882
|
+
export {
|
|
883
|
+
Agent,
|
|
884
|
+
AgentError,
|
|
885
|
+
AgentRecursionError,
|
|
886
|
+
LLMProviderError,
|
|
887
|
+
ReactLoopError,
|
|
888
|
+
createOpenAIProvider,
|
|
889
|
+
createProvider,
|
|
890
|
+
plugin_default as default,
|
|
891
|
+
reactLoop,
|
|
892
|
+
reactLoopStream
|
|
893
|
+
};
|
|
894
|
+
//# sourceMappingURL=index.js.map
|