@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.
@@ -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
- var logger = createLogger("llm");
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) => logger.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
- logger.debug("ConfigComponent does not support watch, hot reload disabled");
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
- logger.debug("Config hot reload watcher registered for llm.*");
1060
+ logger2.debug("Config hot reload watcher registered for llm.*");
606
1061
  }
607
1062
  onConfigChange(event) {
608
- logger.info(`LLM config changed: ${event.key}`);
1063
+ logger2.info(`LLM config changed: ${event.key}`);
609
1064
  if (event.key === "llm.providers" || event.key.startsWith("llm.providers.")) {
610
- logger.info(`LLM provider config changed, will use new config on next call`);
1065
+ logger2.info(`LLM provider config changed, will use new config on next call`);
611
1066
  } else if (event.key === "llm.defaultModel") {
612
- logger.info(`LLM default model changed to: ${event.newValue}`);
1067
+ logger2.info(`LLM default model changed to: ${event.newValue}`);
613
1068
  } else if (event.key === "llm.defaultProvider") {
614
- logger.info(`LLM default provider changed to: ${event.newValue}`);
1069
+ logger2.info(`LLM default provider changed to: ${event.newValue}`);
615
1070
  } else {
616
- logger.debug(`LLM config updated: ${event.key}`);
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
- const result = await invoke(ctx.invokeConfig, {
707
- messages: ctx.messages,
708
- model: ctx.invokeConfig.model,
709
- temperature: ctx.temperature,
710
- maxTokens: ctx.maxTokens,
711
- tools: request.tools,
712
- env: this.env,
713
- context: {
714
- sessionId: request.context?.sessionId,
715
- messageId: request.context?.messageId
716
- },
717
- toolChoice: request.toolChoice
718
- }, { abort: ctx.abortSignal });
719
- if (result.isError) {
720
- throw new Error(result.result);
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 parsed = JSON.parse(result.result);
724
- output.content = parsed.content || "";
725
- output.reasoning = parsed.reasoning || undefined;
726
- output.finishReason = parsed.tool_calls?.length ? "tool-calls" : "stop";
727
- output.toolCalls = parsed.tool_calls || [];
728
- if (parsed.usage) {
729
- finishUsage = {
730
- promptTokens: parsed.usage.promptTokens ?? parsed.usage.inputTokens ?? 0,
731
- completionTokens: parsed.usage.completionTokens ?? parsed.usage.outputTokens ?? 0,
732
- totalTokens: parsed.usage.totalTokens ?? 0
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 };