@ai-setting/roy-agent-core 1.6.42 → 1.7.1
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/env/agent/index.js +3 -4
- package/dist/env/index.js +7 -8
- package/dist/env/llm/index.js +17 -6
- package/dist/env/session/index.js +4 -4
- package/dist/env/session/storage/index.js +3 -3
- package/dist/env/workflow/index.js +6 -6
- package/dist/env/workflow/tools/index.js +5 -5
- package/dist/index.js +13 -14
- package/dist/shared/@ai-setting/{roy-agent-core-c1stgqnw.js → roy-agent-core-2xb3f3ye.js} +143 -23
- package/dist/shared/@ai-setting/{roy-agent-core-fzyn4fxx.js → roy-agent-core-3dwcwgn5.js} +163 -30
- package/dist/shared/@ai-setting/{roy-agent-core-dbwvfe0m.js → roy-agent-core-qatz3q31.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-7wv7gh6t.js → roy-agent-core-w93wrd77.js} +627 -46
- package/dist/shared/@ai-setting/{roy-agent-core-afb03wwp.js → roy-agent-core-wgp3q62x.js} +7 -4
- package/dist/shared/@ai-setting/{roy-agent-core-9yf6we04.js → roy-agent-core-xmacq00w.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-f2nync7q.js → roy-agent-core-xvag5pd0.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-1k9j90s7.js → roy-agent-core-zcx8nf4a.js} +1 -1
- package/package.json +5 -2
- package/dist/shared/@ai-setting/roy-agent-core-12gac5zn.js +0 -44
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EnvSource
|
|
3
3
|
} from "./roy-agent-core-y3g3ar7a.js";
|
|
4
|
-
import {
|
|
5
|
-
invoke
|
|
6
|
-
} from "./roy-agent-core-c1stgqnw.js";
|
|
7
4
|
import {
|
|
8
5
|
ContextError,
|
|
9
6
|
ErrorCodes
|
|
10
7
|
} from "./roy-agent-core-ctdhjv68.js";
|
|
8
|
+
import {
|
|
9
|
+
invoke,
|
|
10
|
+
invokeNonStream
|
|
11
|
+
} from "./roy-agent-core-2xb3f3ye.js";
|
|
11
12
|
import {
|
|
12
13
|
envKeyToConfigKey,
|
|
13
14
|
toEnvKey
|
|
@@ -22,16 +23,467 @@ import {
|
|
|
22
23
|
} from "./roy-agent-core-j29yg06n.js";
|
|
23
24
|
import {
|
|
24
25
|
TracedAs,
|
|
25
|
-
init_decorator
|
|
26
|
+
init_decorator,
|
|
27
|
+
wrapFunction
|
|
26
28
|
} from "./roy-agent-core-gb7pm9c0.js";
|
|
27
29
|
import {
|
|
28
30
|
createLogger,
|
|
29
31
|
init_logger
|
|
30
32
|
} from "./roy-agent-core-kaq037np.js";
|
|
31
33
|
import {
|
|
32
|
-
__legacyDecorateClassTS
|
|
34
|
+
__legacyDecorateClassTS,
|
|
35
|
+
__require
|
|
33
36
|
} from "./roy-agent-core-fs0mn2jk.js";
|
|
34
37
|
|
|
38
|
+
// src/env/llm/adapter.ts
|
|
39
|
+
var ADAPTER_NAMES = ["pi-ai", "ai-sdk"];
|
|
40
|
+
// src/env/llm/adapter-pi-ai.ts
|
|
41
|
+
init_decorator();
|
|
42
|
+
init_logger();
|
|
43
|
+
import {
|
|
44
|
+
complete,
|
|
45
|
+
stream as piStream,
|
|
46
|
+
getModel
|
|
47
|
+
} from "@earendil-works/pi-ai";
|
|
48
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
49
|
+
var logger = createLogger("llm:adapter-pi-ai");
|
|
50
|
+
var KNOWN_PROVIDER_APIS = {
|
|
51
|
+
anthropic: { api: "anthropic-messages", contextLimit: 200000, maxTokensLimit: 8192 },
|
|
52
|
+
openai: { api: "openai-responses", contextLimit: 128000, maxTokensLimit: 16384 },
|
|
53
|
+
google: { api: "google-generative-ai", contextLimit: 1e6, maxTokensLimit: 8192 },
|
|
54
|
+
"google-vertex": { api: "google-vertex", contextLimit: 1e6, maxTokensLimit: 8192 },
|
|
55
|
+
deepseek: { api: "openai-completions", contextLimit: 64000, maxTokensLimit: 8192 },
|
|
56
|
+
xai: { api: "openai-completions", contextLimit: 131072, maxTokensLimit: 8192 },
|
|
57
|
+
groq: { api: "openai-completions", contextLimit: 32768, maxTokensLimit: 8192 },
|
|
58
|
+
minimax: { api: "openai-completions", contextLimit: 128000, maxTokensLimit: 8192 },
|
|
59
|
+
"minimax-cn": { api: "openai-completions", contextLimit: 128000, maxTokensLimit: 8192 },
|
|
60
|
+
moonshotai: { api: "openai-completions", contextLimit: 128000, maxTokensLimit: 8192 },
|
|
61
|
+
"kimi-coding": { api: "openai-completions", contextLimit: 128000, maxTokensLimit: 8192 }
|
|
62
|
+
};
|
|
63
|
+
function resolvePiModel(provider, modelId, baseURL) {
|
|
64
|
+
const builtIn = getModel(provider, modelId);
|
|
65
|
+
if (builtIn)
|
|
66
|
+
return baseURL ? { ...builtIn, baseUrl: baseURL } : builtIn;
|
|
67
|
+
const providerApiOverride = {
|
|
68
|
+
"minimax-responses": "openai-responses",
|
|
69
|
+
"minimax-openai": "openai-completions"
|
|
70
|
+
};
|
|
71
|
+
let configuredApi;
|
|
72
|
+
if (providerApiOverride[provider]) {
|
|
73
|
+
configuredApi = providerApiOverride[provider];
|
|
74
|
+
} else if (provider === "minimax" || provider === "minimax-cn") {
|
|
75
|
+
configuredApi = baseURL?.includes("anthropic") ? "anthropic-messages" : "openai-completions";
|
|
76
|
+
} else if (baseURL?.includes("/responses")) {
|
|
77
|
+
configuredApi = "openai-responses";
|
|
78
|
+
} else if (baseURL?.includes("anthropic")) {
|
|
79
|
+
configuredApi = "anthropic-messages";
|
|
80
|
+
} else {
|
|
81
|
+
configuredApi = KNOWN_PROVIDER_APIS[provider]?.api || "openai-completions";
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
id: modelId,
|
|
85
|
+
name: modelId,
|
|
86
|
+
api: configuredApi,
|
|
87
|
+
provider,
|
|
88
|
+
baseUrl: baseURL || "",
|
|
89
|
+
reasoning: false,
|
|
90
|
+
input: ["text"],
|
|
91
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
92
|
+
contextWindow: KNOWN_PROVIDER_APIS[provider]?.contextLimit || 128000,
|
|
93
|
+
maxTokens: KNOWN_PROVIDER_APIS[provider]?.maxTokensLimit || 8192
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function toPiMessages(messages) {
|
|
97
|
+
const out = [];
|
|
98
|
+
const ts = Date.now();
|
|
99
|
+
for (const m of messages) {
|
|
100
|
+
if (m.role === "system")
|
|
101
|
+
continue;
|
|
102
|
+
if (m.role === "user") {
|
|
103
|
+
if (typeof m.content === "string") {
|
|
104
|
+
out.push({ role: "user", content: m.content, timestamp: ts });
|
|
105
|
+
} else {
|
|
106
|
+
out.push({ role: "user", content: m.content.flatMap((c) => {
|
|
107
|
+
if (c.type === "text")
|
|
108
|
+
return [{ type: "text", text: c.text }];
|
|
109
|
+
if (c.type === "image")
|
|
110
|
+
return [{ type: "image", data: c.image instanceof Uint8Array ? Buffer.from(c.image).toString("base64") : c.image, mimeType: "image/*" }];
|
|
111
|
+
return [{ type: "text", text: `[file:${c.mediaType}] ${typeof c.file === "string" ? c.file : Buffer.from(c.file).toString("base64")}` }];
|
|
112
|
+
}), timestamp: ts });
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (m.role === "tool") {
|
|
117
|
+
out.push({ role: "toolResult", toolCallId: m.toolCallId || "", toolName: m.name || "", content: [{ type: "text", text: typeof m.content === "string" ? m.content : JSON.stringify(m.content) }], isError: false, timestamp: ts });
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const content = [];
|
|
121
|
+
if (typeof m.content === "string" && m.content)
|
|
122
|
+
content.push({ type: "text", text: m.content });
|
|
123
|
+
else if (Array.isArray(m.content))
|
|
124
|
+
for (const c of m.content) {
|
|
125
|
+
if (c.type === "text")
|
|
126
|
+
content.push({ type: "text", text: c.text });
|
|
127
|
+
else if (c.type === "image")
|
|
128
|
+
content.push({ type: "image", data: c.image instanceof Uint8Array ? Buffer.from(c.image).toString("base64") : c.image, mimeType: "image/*" });
|
|
129
|
+
else
|
|
130
|
+
content.push({ type: "text", text: `[file:${c.mediaType}] ${typeof c.file === "string" ? c.file : Buffer.from(c.file).toString("base64")}` });
|
|
131
|
+
}
|
|
132
|
+
for (const tc of m.toolCalls || [])
|
|
133
|
+
content.push({ type: "toolCall", id: tc.id, name: tc.name, arguments: safeJson(tc.arguments) });
|
|
134
|
+
out.push({ role: "assistant", content, api: "openai-completions", model: "", provider: "", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, stopReason: "stop", timestamp: ts });
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
function extractSystemPrompt(messages) {
|
|
139
|
+
const prompts = messages.filter((m) => m.role === "system").map((m) => typeof m.content === "string" ? m.content : m.content.filter((c) => c.type === "text").map((c) => c.text).join(""));
|
|
140
|
+
return prompts.length ? prompts.join(`
|
|
141
|
+
|
|
142
|
+
`) : undefined;
|
|
143
|
+
}
|
|
144
|
+
function safeJson(s) {
|
|
145
|
+
try {
|
|
146
|
+
return JSON.parse(s);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
logger.warn(`[safeJson] JSON.parse failed`, {
|
|
149
|
+
parse_failed: true,
|
|
150
|
+
input_length: s.length,
|
|
151
|
+
error: e.message
|
|
152
|
+
});
|
|
153
|
+
return {};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function isZodSchema(obj) {
|
|
157
|
+
return !!obj && typeof obj === "object" && "_def" in obj;
|
|
158
|
+
}
|
|
159
|
+
function convertToolParameters(parameters) {
|
|
160
|
+
if (parameters == null)
|
|
161
|
+
return { type: "object", properties: {} };
|
|
162
|
+
if (typeof parameters !== "object")
|
|
163
|
+
return { type: "object", properties: {} };
|
|
164
|
+
if (isZodSchema(parameters)) {
|
|
165
|
+
try {
|
|
166
|
+
const jsonSchema = zodToJsonSchema(parameters, "zod");
|
|
167
|
+
if ("$ref" in jsonSchema && jsonSchema.definitions) {
|
|
168
|
+
const def = jsonSchema.definitions.zod;
|
|
169
|
+
if (def && def.type === "object" && def.properties) {
|
|
170
|
+
return def;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return jsonSchema;
|
|
174
|
+
} catch {
|
|
175
|
+
return { type: "object", properties: {} };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const paramsObj = parameters;
|
|
179
|
+
if (paramsObj.type === "object" && paramsObj.properties) {
|
|
180
|
+
return paramsObj;
|
|
181
|
+
}
|
|
182
|
+
return { type: "object", properties: {} };
|
|
183
|
+
}
|
|
184
|
+
function toPiTools(tools) {
|
|
185
|
+
if (!tools)
|
|
186
|
+
return;
|
|
187
|
+
return tools.map((t) => ({
|
|
188
|
+
name: t.name,
|
|
189
|
+
description: t.description || "",
|
|
190
|
+
parameters: convertToolParameters(t.parameters)
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
function fromAssistantMessage(msg) {
|
|
194
|
+
let text = "";
|
|
195
|
+
const toolCalls = [];
|
|
196
|
+
for (const block of msg.content) {
|
|
197
|
+
const b = block;
|
|
198
|
+
if (b.type === "text") {
|
|
199
|
+
text += b.text || "";
|
|
200
|
+
} else if (b.type === "toolCall") {
|
|
201
|
+
toolCalls.push({
|
|
202
|
+
id: b.id || "",
|
|
203
|
+
name: b.name || "",
|
|
204
|
+
arguments: typeof b.arguments === "string" ? b.arguments : JSON.stringify(b.arguments ?? {})
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
text,
|
|
210
|
+
finishReason: msg.stopReason || "stop",
|
|
211
|
+
toolCalls: toolCalls.length ? toolCalls : undefined,
|
|
212
|
+
usage: {
|
|
213
|
+
promptTokens: msg.usage?.input ?? 0,
|
|
214
|
+
completionTokens: msg.usage?.output ?? 0,
|
|
215
|
+
totalTokens: msg.usage?.totalTokens ?? 0,
|
|
216
|
+
cacheRead: msg.usage?.cacheRead,
|
|
217
|
+
cacheWrite: msg.usage?.cacheWrite
|
|
218
|
+
},
|
|
219
|
+
raw: msg
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
class EmbeddingNotSupportedError extends Error {
|
|
224
|
+
constructor(adapter) {
|
|
225
|
+
super(`Embedding is not supported by ${adapter}; use AiSdkAdapter.embed() or apply at higher layer.`);
|
|
226
|
+
this.name = "EmbeddingNotSupportedError";
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
class PiAiAdapter {
|
|
231
|
+
name = "pi-ai";
|
|
232
|
+
async chat(req) {
|
|
233
|
+
const model = resolvePiModel(req.provider, req.modelId, req.baseURL);
|
|
234
|
+
const ctx = {
|
|
235
|
+
systemPrompt: [req.systemPrompt, extractSystemPrompt(req.messages)].filter(Boolean).join(`
|
|
236
|
+
|
|
237
|
+
`) || undefined,
|
|
238
|
+
messages: toPiMessages(req.messages),
|
|
239
|
+
tools: toPiTools(req.tools)
|
|
240
|
+
};
|
|
241
|
+
const msg = await complete(model, ctx, {
|
|
242
|
+
apiKey: req.apiKey,
|
|
243
|
+
baseURL: req.baseURL,
|
|
244
|
+
temperature: req.temperature,
|
|
245
|
+
maxTokens: req.maxTokens,
|
|
246
|
+
signal: req.signal
|
|
247
|
+
});
|
|
248
|
+
return fromAssistantMessage(msg);
|
|
249
|
+
}
|
|
250
|
+
async stream(req) {
|
|
251
|
+
const model = resolvePiModel(req.provider, req.modelId, req.baseURL);
|
|
252
|
+
const ctx = {
|
|
253
|
+
systemPrompt: [req.systemPrompt, extractSystemPrompt(req.messages)].filter(Boolean).join(`
|
|
254
|
+
|
|
255
|
+
`) || undefined,
|
|
256
|
+
messages: toPiMessages(req.messages),
|
|
257
|
+
tools: toPiTools(req.tools)
|
|
258
|
+
};
|
|
259
|
+
const eventStream = piStream(model, ctx, { apiKey: req.apiKey, baseURL: req.baseURL, temperature: req.temperature, maxTokens: req.maxTokens, signal: req.signal });
|
|
260
|
+
return { events: eventStream, result: async () => fromAssistantMessage(await eventStream.result()) };
|
|
261
|
+
}
|
|
262
|
+
async embed(_req) {
|
|
263
|
+
throw new EmbeddingNotSupportedError(this.name);
|
|
264
|
+
}
|
|
265
|
+
async resolveProvider(provider, modelId) {
|
|
266
|
+
const model = resolvePiModel(provider, modelId);
|
|
267
|
+
const caps = KNOWN_PROVIDER_APIS[provider] || {
|
|
268
|
+
api: model.api,
|
|
269
|
+
contextLimit: model.contextWindow,
|
|
270
|
+
maxTokensLimit: model.maxTokens
|
|
271
|
+
};
|
|
272
|
+
return {
|
|
273
|
+
api: model.api,
|
|
274
|
+
provider,
|
|
275
|
+
modelId,
|
|
276
|
+
contextLimit: caps.contextLimit,
|
|
277
|
+
maxTokensLimit: caps.maxTokensLimit
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
__legacyDecorateClassTS([
|
|
282
|
+
TracedAs("llm.adapter.chat", {
|
|
283
|
+
recordParams: true,
|
|
284
|
+
recordResult: true,
|
|
285
|
+
log: true,
|
|
286
|
+
paramFilter: (args) => {
|
|
287
|
+
const req = args[0];
|
|
288
|
+
return {
|
|
289
|
+
provider: req?.provider,
|
|
290
|
+
model_id: req?.modelId,
|
|
291
|
+
messages_count: req?.messages?.length,
|
|
292
|
+
tools_count: req?.tools?.length,
|
|
293
|
+
temperature: req?.temperature,
|
|
294
|
+
max_tokens: req?.maxTokens
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
resultFilter: (r) => ({
|
|
298
|
+
finish_reason: r?.finishReason,
|
|
299
|
+
usage_input: r?.usage?.promptTokens,
|
|
300
|
+
usage_output: r?.usage?.completionTokens,
|
|
301
|
+
usage_total: r?.usage?.totalTokens,
|
|
302
|
+
content_length: r?.text?.length
|
|
303
|
+
})
|
|
304
|
+
})
|
|
305
|
+
], PiAiAdapter.prototype, "chat", null);
|
|
306
|
+
__legacyDecorateClassTS([
|
|
307
|
+
TracedAs("llm.adapter.stream", {
|
|
308
|
+
recordParams: true,
|
|
309
|
+
recordResult: false,
|
|
310
|
+
log: true,
|
|
311
|
+
paramFilter: (args) => {
|
|
312
|
+
const req = args[0];
|
|
313
|
+
return {
|
|
314
|
+
provider: req?.provider,
|
|
315
|
+
model_id: req?.modelId,
|
|
316
|
+
messages_count: req?.messages?.length
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
], PiAiAdapter.prototype, "stream", null);
|
|
321
|
+
__legacyDecorateClassTS([
|
|
322
|
+
TracedAs("llm.adapter.embed", { recordParams: true, recordResult: false, log: true })
|
|
323
|
+
], PiAiAdapter.prototype, "embed", null);
|
|
324
|
+
__legacyDecorateClassTS([
|
|
325
|
+
TracedAs("llm.adapter.resolve_provider", {
|
|
326
|
+
recordParams: true,
|
|
327
|
+
recordResult: true,
|
|
328
|
+
log: true,
|
|
329
|
+
paramFilter: (args) => ({
|
|
330
|
+
provider: args[0],
|
|
331
|
+
model_id: args[1]
|
|
332
|
+
}),
|
|
333
|
+
resultFilter: (r) => ({
|
|
334
|
+
api: r?.api,
|
|
335
|
+
context_limit: r?.contextLimit,
|
|
336
|
+
max_tokens_limit: r?.maxTokensLimit
|
|
337
|
+
})
|
|
338
|
+
})
|
|
339
|
+
], PiAiAdapter.prototype, "resolveProvider", null);
|
|
340
|
+
// src/env/llm/adapter-ai-sdk.ts
|
|
341
|
+
init_decorator();
|
|
342
|
+
import { embed as aiEmbed, embedMany as aiEmbedMany } from "ai";
|
|
343
|
+
class AiSdkAdapter {
|
|
344
|
+
name = "ai-sdk";
|
|
345
|
+
async chat(req) {
|
|
346
|
+
const config = this.toInvokeConfig(req);
|
|
347
|
+
const opts = this.toInvokeOptions(req);
|
|
348
|
+
const ctx = { abort: req.signal };
|
|
349
|
+
const out = await invokeNonStream(config, opts, ctx);
|
|
350
|
+
return {
|
|
351
|
+
text: out.content || "",
|
|
352
|
+
finishReason: out.finishReason || "stop",
|
|
353
|
+
toolCalls: out.toolCalls?.map((tc) => ({
|
|
354
|
+
id: tc.id,
|
|
355
|
+
name: tc.name,
|
|
356
|
+
arguments: tc.arguments
|
|
357
|
+
})),
|
|
358
|
+
usage: out.usage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
359
|
+
raw: out
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
async stream(req) {
|
|
363
|
+
const config = this.toInvokeConfig(req);
|
|
364
|
+
const opts = this.toInvokeOptions(req);
|
|
365
|
+
const ctx = { abort: req.signal };
|
|
366
|
+
let output;
|
|
367
|
+
const events = async function* () {
|
|
368
|
+
output = await invoke(config, opts, ctx);
|
|
369
|
+
yield output;
|
|
370
|
+
}();
|
|
371
|
+
return {
|
|
372
|
+
events,
|
|
373
|
+
result: async () => ({
|
|
374
|
+
text: output?.content || "",
|
|
375
|
+
finishReason: output?.finishReason || "stop",
|
|
376
|
+
toolCalls: output?.toolCalls?.map((tc) => ({
|
|
377
|
+
id: tc.id,
|
|
378
|
+
name: tc.name,
|
|
379
|
+
arguments: tc.arguments
|
|
380
|
+
})),
|
|
381
|
+
usage: output?.usage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
382
|
+
raw: output
|
|
383
|
+
})
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
async embed(req) {
|
|
387
|
+
const { createOpenAICompatible } = await import("@ai-sdk/openai-compatible");
|
|
388
|
+
const provider = createOpenAICompatible({ name: req.provider, apiKey: req.apiKey, baseURL: req.baseURL });
|
|
389
|
+
const model = provider.textEmbeddingModel(req.modelId);
|
|
390
|
+
const values = Array.isArray(req.input) ? req.input : [req.input];
|
|
391
|
+
const result = values.length === 1 ? { embeddings: [(await aiEmbed({ model, value: values[0], abortSignal: req.signal })).embedding] } : await aiEmbedMany({ model, values, abortSignal: req.signal });
|
|
392
|
+
return { embeddings: result.embeddings, usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 } };
|
|
393
|
+
}
|
|
394
|
+
async resolveProvider(provider, modelId) {
|
|
395
|
+
return {
|
|
396
|
+
api: provider === "anthropic" ? "anthropic-messages" : "openai-completions",
|
|
397
|
+
provider,
|
|
398
|
+
modelId,
|
|
399
|
+
contextLimit: 0,
|
|
400
|
+
maxTokensLimit: 0
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
toInvokeConfig(req) {
|
|
404
|
+
return {
|
|
405
|
+
model: `${req.provider}/${req.modelId}`,
|
|
406
|
+
baseURL: req.baseURL,
|
|
407
|
+
apiKey: req.apiKey
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
toInvokeOptions(req) {
|
|
411
|
+
return {
|
|
412
|
+
messages: req.messages,
|
|
413
|
+
tools: req.tools,
|
|
414
|
+
temperature: req.temperature,
|
|
415
|
+
maxTokens: req.maxTokens,
|
|
416
|
+
model: req.modelId
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
__legacyDecorateClassTS([
|
|
421
|
+
TracedAs("llm.adapter.ai_sdk.chat", {
|
|
422
|
+
recordParams: true,
|
|
423
|
+
recordResult: true,
|
|
424
|
+
log: true,
|
|
425
|
+
paramFilter: (args) => {
|
|
426
|
+
const req = args[0];
|
|
427
|
+
return {
|
|
428
|
+
provider: req?.provider,
|
|
429
|
+
model_id: req?.modelId,
|
|
430
|
+
messages_count: req?.messages?.length
|
|
431
|
+
};
|
|
432
|
+
},
|
|
433
|
+
resultFilter: (r) => ({
|
|
434
|
+
finish_reason: r?.finishReason,
|
|
435
|
+
usage_input: r?.usage?.promptTokens,
|
|
436
|
+
usage_output: r?.usage?.completionTokens
|
|
437
|
+
})
|
|
438
|
+
})
|
|
439
|
+
], AiSdkAdapter.prototype, "chat", null);
|
|
440
|
+
__legacyDecorateClassTS([
|
|
441
|
+
TracedAs("llm.adapter.ai_sdk.stream", {
|
|
442
|
+
recordParams: true,
|
|
443
|
+
recordResult: false,
|
|
444
|
+
log: true
|
|
445
|
+
})
|
|
446
|
+
], AiSdkAdapter.prototype, "stream", null);
|
|
447
|
+
__legacyDecorateClassTS([
|
|
448
|
+
TracedAs("llm.adapter.ai_sdk.embed", { recordParams: true, recordResult: true, log: true })
|
|
449
|
+
], AiSdkAdapter.prototype, "embed", null);
|
|
450
|
+
__legacyDecorateClassTS([
|
|
451
|
+
TracedAs("llm.adapter.ai_sdk.resolve_provider", {
|
|
452
|
+
recordParams: true,
|
|
453
|
+
recordResult: true,
|
|
454
|
+
log: true
|
|
455
|
+
})
|
|
456
|
+
], AiSdkAdapter.prototype, "resolveProvider", null);
|
|
457
|
+
// src/env/llm/factory.ts
|
|
458
|
+
init_decorator();
|
|
459
|
+
var runtimeOverride;
|
|
460
|
+
function setLLMProvider(name) {
|
|
461
|
+
runtimeOverride = name;
|
|
462
|
+
}
|
|
463
|
+
function getLLMProviderOverride() {
|
|
464
|
+
return runtimeOverride;
|
|
465
|
+
}
|
|
466
|
+
var _createLLMAdapter = function _createLLMAdapter2() {
|
|
467
|
+
const arg = runtimeOverride ?? process.env.ROY_LLM_PROVIDER ?? "pi-ai";
|
|
468
|
+
switch (arg) {
|
|
469
|
+
case "pi-ai":
|
|
470
|
+
return new PiAiAdapter;
|
|
471
|
+
case "ai-sdk":
|
|
472
|
+
return new AiSdkAdapter;
|
|
473
|
+
default:
|
|
474
|
+
throw new Error(`Invalid ROY_LLM_PROVIDER value: "${arg}". Expected: pi-ai | ai-sdk`);
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
var createLLMAdapter = wrapFunction(_createLLMAdapter, "llm.adapter.factory", {
|
|
478
|
+
recordParams: false,
|
|
479
|
+
recordResult: true,
|
|
480
|
+
log: true,
|
|
481
|
+
resultFilter: (_r) => ({
|
|
482
|
+
adapter_name: _r?.name,
|
|
483
|
+
env_arg: process.env.ROY_LLM_PROVIDER ?? null,
|
|
484
|
+
runtime_override: runtimeOverride ?? null
|
|
485
|
+
})
|
|
486
|
+
});
|
|
35
487
|
// src/env/llm/types.ts
|
|
36
488
|
import { z } from "zod";
|
|
37
489
|
var ProviderCapabilitiesSchema = z.object({
|
|
@@ -450,13 +902,16 @@ class LLMHooks {
|
|
|
450
902
|
// src/env/llm/llm.ts
|
|
451
903
|
init_logger();
|
|
452
904
|
init_decorator();
|
|
453
|
-
|
|
905
|
+
import { randomUUID } from "crypto";
|
|
906
|
+
var logger2 = createLogger("llm");
|
|
454
907
|
|
|
455
908
|
class LLMComponent extends BaseComponent {
|
|
456
909
|
name = "llm";
|
|
457
910
|
version = "2.0.0";
|
|
458
911
|
configComponent;
|
|
459
912
|
configWatcher;
|
|
913
|
+
cachedAdapter;
|
|
914
|
+
cachedAdapterProvider;
|
|
460
915
|
constructor() {
|
|
461
916
|
super();
|
|
462
917
|
}
|
|
@@ -562,7 +1017,7 @@ class LLMComponent extends BaseComponent {
|
|
|
562
1017
|
componentName: "llm",
|
|
563
1018
|
knownKeys,
|
|
564
1019
|
logger: {
|
|
565
|
-
warn: (msg) =>
|
|
1020
|
+
warn: (msg) => logger2.warn(msg)
|
|
566
1021
|
}
|
|
567
1022
|
});
|
|
568
1023
|
}
|
|
@@ -596,24 +1051,24 @@ class LLMComponent extends BaseComponent {
|
|
|
596
1051
|
}
|
|
597
1052
|
registerConfigWatcher(configComponent) {
|
|
598
1053
|
if (typeof configComponent.watch !== "function") {
|
|
599
|
-
|
|
1054
|
+
logger2.debug("ConfigComponent does not support watch, hot reload disabled");
|
|
600
1055
|
return;
|
|
601
1056
|
}
|
|
602
1057
|
this.configWatcher = configComponent.watch("llm.*", (event) => {
|
|
603
1058
|
this.onConfigChange(event);
|
|
604
1059
|
});
|
|
605
|
-
|
|
1060
|
+
logger2.debug("Config hot reload watcher registered for llm.*");
|
|
606
1061
|
}
|
|
607
1062
|
onConfigChange(event) {
|
|
608
|
-
|
|
1063
|
+
logger2.info(`LLM config changed: ${event.key}`);
|
|
609
1064
|
if (event.key === "llm.providers" || event.key.startsWith("llm.providers.")) {
|
|
610
|
-
|
|
1065
|
+
logger2.info(`LLM provider config changed, will use new config on next call`);
|
|
611
1066
|
} else if (event.key === "llm.defaultModel") {
|
|
612
|
-
|
|
1067
|
+
logger2.info(`LLM default model changed to: ${event.newValue}`);
|
|
613
1068
|
} else if (event.key === "llm.defaultProvider") {
|
|
614
|
-
|
|
1069
|
+
logger2.info(`LLM default provider changed to: ${event.newValue}`);
|
|
615
1070
|
} else {
|
|
616
|
-
|
|
1071
|
+
logger2.debug(`LLM config updated: ${event.key}`);
|
|
617
1072
|
}
|
|
618
1073
|
}
|
|
619
1074
|
async onStart() {
|
|
@@ -691,52 +1146,177 @@ class LLMComponent extends BaseComponent {
|
|
|
691
1146
|
abortSignal: request.abortSignal
|
|
692
1147
|
};
|
|
693
1148
|
}
|
|
1149
|
+
getAdapter() {
|
|
1150
|
+
const currentProvider = process.env.ROY_LLM_PROVIDER || "pi-ai";
|
|
1151
|
+
if (this.cachedAdapter && this.cachedAdapterProvider === currentProvider) {
|
|
1152
|
+
return this.cachedAdapter;
|
|
1153
|
+
}
|
|
1154
|
+
this.cachedAdapter = createLLMAdapter();
|
|
1155
|
+
this.cachedAdapterProvider = currentProvider;
|
|
1156
|
+
return this.cachedAdapter;
|
|
1157
|
+
}
|
|
1158
|
+
invalidateAdapterCache() {
|
|
1159
|
+
this.cachedAdapter = undefined;
|
|
1160
|
+
this.cachedAdapterProvider = undefined;
|
|
1161
|
+
}
|
|
694
1162
|
async invoke(request) {
|
|
695
1163
|
const startTime = Date.now();
|
|
696
1164
|
if (this._status !== "running") {
|
|
697
1165
|
throw new Error("LLMComponent is not running");
|
|
698
1166
|
}
|
|
699
1167
|
const ctx = this.resolveRequest(request);
|
|
1168
|
+
const adapter = this.getAdapter();
|
|
1169
|
+
const chatReq = {
|
|
1170
|
+
provider: ctx.providerId,
|
|
1171
|
+
modelId: ctx.model,
|
|
1172
|
+
messages: ctx.messages,
|
|
1173
|
+
tools: request.tools,
|
|
1174
|
+
systemPrompt: undefined,
|
|
1175
|
+
temperature: ctx.temperature,
|
|
1176
|
+
maxTokens: ctx.maxTokens,
|
|
1177
|
+
apiKey: ctx.invokeConfig.apiKey || "",
|
|
1178
|
+
baseURL: ctx.invokeConfig.baseURL,
|
|
1179
|
+
signal: ctx.abortSignal
|
|
1180
|
+
};
|
|
700
1181
|
let output = {
|
|
701
1182
|
content: "",
|
|
702
1183
|
reasoning: undefined,
|
|
703
1184
|
finishReason: "stop"
|
|
704
1185
|
};
|
|
705
1186
|
let finishUsage;
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
1187
|
+
let resp;
|
|
1188
|
+
try {
|
|
1189
|
+
const streamHandle = await adapter.stream(chatReq);
|
|
1190
|
+
resp = await this.consumeStreamAndGetResult(streamHandle, request, ctx);
|
|
1191
|
+
if (resp.finishReason === "error") {
|
|
1192
|
+
throw new Error(resp.raw?.errorMessage || "adapter returned an error response");
|
|
1193
|
+
}
|
|
1194
|
+
} catch (adapterErr) {
|
|
1195
|
+
logger2.warn(`[LLMComponent.invoke] adapter(${adapter.name}) failed, falling back to AI SDK invoke: ${adapterErr.message}`);
|
|
1196
|
+
const result = await invoke(ctx.invokeConfig, {
|
|
1197
|
+
messages: ctx.messages,
|
|
1198
|
+
model: ctx.invokeConfig.model,
|
|
1199
|
+
temperature: ctx.temperature,
|
|
1200
|
+
maxTokens: ctx.maxTokens,
|
|
1201
|
+
tools: request.tools,
|
|
1202
|
+
env: this.env,
|
|
1203
|
+
context: {
|
|
1204
|
+
sessionId: request.context?.sessionId,
|
|
1205
|
+
messageId: request.context?.messageId
|
|
1206
|
+
},
|
|
1207
|
+
toolChoice: request.toolChoice
|
|
1208
|
+
}, { abort: ctx.abortSignal });
|
|
1209
|
+
if (result.isError) {
|
|
1210
|
+
throw new Error(result.result);
|
|
1211
|
+
}
|
|
1212
|
+
try {
|
|
1213
|
+
const parsed = JSON.parse(result.result);
|
|
1214
|
+
output.content = parsed.content || "";
|
|
1215
|
+
output.reasoning = parsed.reasoning || undefined;
|
|
1216
|
+
output.finishReason = parsed.tool_calls?.length ? "tool-calls" : "stop";
|
|
1217
|
+
output.toolCalls = parsed.tool_calls || [];
|
|
1218
|
+
if (parsed.usage) {
|
|
1219
|
+
finishUsage = {
|
|
1220
|
+
promptTokens: parsed.usage.promptTokens ?? parsed.usage.inputTokens ?? 0,
|
|
1221
|
+
completionTokens: parsed.usage.completionTokens ?? parsed.usage.outputTokens ?? 0,
|
|
1222
|
+
totalTokens: parsed.usage.totalTokens ?? 0
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
output.usage = finishUsage;
|
|
1226
|
+
} catch {
|
|
1227
|
+
output.content = result.result;
|
|
1228
|
+
output.finishReason = "stop";
|
|
1229
|
+
}
|
|
1230
|
+
return this.finalizeOutput(request, ctx, output, finishUsage, startTime, `adapter-fallback:${adapter.name}`);
|
|
721
1231
|
}
|
|
1232
|
+
output.content = resp.text || "";
|
|
1233
|
+
output.finishReason = resp.finishReason;
|
|
1234
|
+
output.toolCalls = resp.toolCalls || [];
|
|
1235
|
+
if (resp.usage) {
|
|
1236
|
+
finishUsage = {
|
|
1237
|
+
promptTokens: resp.usage.promptTokens ?? 0,
|
|
1238
|
+
completionTokens: resp.usage.completionTokens ?? 0,
|
|
1239
|
+
totalTokens: resp.usage.totalTokens ?? 0
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
output.usage = finishUsage;
|
|
1243
|
+
return this.finalizeOutput(request, ctx, output, finishUsage, startTime, adapter.name);
|
|
1244
|
+
}
|
|
1245
|
+
async consumeStreamAndGetResult(streamHandle, request, ctx) {
|
|
1246
|
+
let accumulatedText = "";
|
|
1247
|
+
let accumulatedReasoning = "";
|
|
1248
|
+
let capturedResult;
|
|
1249
|
+
const emitEnvEvent = (fullType, payload) => {
|
|
1250
|
+
try {
|
|
1251
|
+
this.env?.pushEnvEvent?.({
|
|
1252
|
+
id: randomUUID(),
|
|
1253
|
+
type: fullType,
|
|
1254
|
+
timestamp: Date.now(),
|
|
1255
|
+
metadata: {
|
|
1256
|
+
source: "llm.adapter.stream",
|
|
1257
|
+
sessionId: request.context?.sessionId,
|
|
1258
|
+
messageId: request.context?.messageId,
|
|
1259
|
+
provider: ctx.providerId,
|
|
1260
|
+
model: ctx.model
|
|
1261
|
+
},
|
|
1262
|
+
payload
|
|
1263
|
+
});
|
|
1264
|
+
} catch (err) {
|
|
1265
|
+
logger2.warn(`Failed to push env event ${fullType}`, { error: String(err) });
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
722
1268
|
try {
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
1269
|
+
for await (const rawEvent of streamHandle.events) {
|
|
1270
|
+
const event = rawEvent;
|
|
1271
|
+
if (!event || typeof event !== "object")
|
|
1272
|
+
continue;
|
|
1273
|
+
switch (event.type) {
|
|
1274
|
+
case "text_delta": {
|
|
1275
|
+
if (typeof event.delta === "string" && event.delta.length > 0) {
|
|
1276
|
+
accumulatedText += event.delta;
|
|
1277
|
+
emitEnvEvent("llm.text", {
|
|
1278
|
+
type: "text",
|
|
1279
|
+
content: accumulatedText,
|
|
1280
|
+
delta: event.delta,
|
|
1281
|
+
contentIndex: event.contentIndex
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
break;
|
|
1285
|
+
}
|
|
1286
|
+
case "thinking_delta": {
|
|
1287
|
+
if (typeof event.delta === "string" && event.delta.length > 0) {
|
|
1288
|
+
accumulatedReasoning += event.delta;
|
|
1289
|
+
emitEnvEvent("llm.reasoning", {
|
|
1290
|
+
type: "reasoning",
|
|
1291
|
+
content: accumulatedReasoning,
|
|
1292
|
+
delta: event.delta,
|
|
1293
|
+
contentIndex: event.contentIndex
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
break;
|
|
1297
|
+
}
|
|
1298
|
+
default:
|
|
1299
|
+
break;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
} finally {
|
|
1303
|
+
try {
|
|
1304
|
+
capturedResult = await streamHandle.result();
|
|
1305
|
+
} catch (resultErr) {
|
|
1306
|
+
if (accumulatedText) {
|
|
1307
|
+
capturedResult = {
|
|
1308
|
+
text: accumulatedText,
|
|
1309
|
+
finishReason: "aborted",
|
|
1310
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
|
|
1311
|
+
};
|
|
1312
|
+
} else {
|
|
1313
|
+
throw resultErr;
|
|
1314
|
+
}
|
|
734
1315
|
}
|
|
735
|
-
output.usage = finishUsage;
|
|
736
|
-
} catch {
|
|
737
|
-
output.content = result.result;
|
|
738
|
-
output.finishReason = "stop";
|
|
739
1316
|
}
|
|
1317
|
+
return capturedResult;
|
|
1318
|
+
}
|
|
1319
|
+
finalizeOutput(request, ctx, output, finishUsage, startTime, adapterName) {
|
|
740
1320
|
const latencyMs = Date.now() - startTime;
|
|
741
1321
|
if (!request.skipThresholdCheck) {
|
|
742
1322
|
const sessionId = request.context?.sessionId;
|
|
@@ -746,7 +1326,8 @@ class LLMComponent extends BaseComponent {
|
|
|
746
1326
|
content: output.content,
|
|
747
1327
|
reasoning: output.reasoning,
|
|
748
1328
|
toolCalls: output.toolCalls,
|
|
749
|
-
usage: output.usage
|
|
1329
|
+
usage: output.usage,
|
|
1330
|
+
adapter: adapterName
|
|
750
1331
|
};
|
|
751
1332
|
throw contextError;
|
|
752
1333
|
}
|
|
@@ -810,4 +1391,4 @@ class LLMComponent extends BaseComponent {
|
|
|
810
1391
|
__legacyDecorateClassTS([
|
|
811
1392
|
TracedAs("llm.component.invoke", { recordParams: true, recordResult: true, log: true })
|
|
812
1393
|
], LLMComponent.prototype, "invoke", null);
|
|
813
|
-
export { ProviderCapabilitiesSchema, ModelLimitsSchema, ProviderConfigSchema, LLMDefaultConfigSchema, LLMConfigSchema, ProviderManager, LLMTransform, LLMHooks, LLMComponent };
|
|
1394
|
+
export { ADAPTER_NAMES, PiAiAdapter, AiSdkAdapter, setLLMProvider, getLLMProviderOverride, createLLMAdapter, ProviderCapabilitiesSchema, ModelLimitsSchema, ProviderConfigSchema, LLMDefaultConfigSchema, LLMConfigSchema, ProviderManager, LLMTransform, LLMHooks, LLMComponent };
|