@nebutra/agents 1.0.0 → 1.1.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/.turbo/turbo-build.log +40 -0
- package/.turbo/turbo-test.log +19 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +36 -0
- package/dist/agent-CahnMASx.d.ts +32 -0
- package/dist/chunk-5JZJ5KMC.js +37 -0
- package/dist/chunk-5LX742GP.js +224 -0
- package/dist/chunk-B7XWL35G.js +60 -0
- package/dist/chunk-NPQECBXL.js +82 -0
- package/dist/chunk-NVPE5EDI.js +42 -0
- package/dist/chunk-RDOFKRI6.js +166 -0
- package/dist/chunk-RLWM437Q.js +61 -0
- package/dist/chunk-UVL2UVVM.js +56 -0
- package/dist/index.d.ts +476 -0
- package/dist/index.js +533 -0
- package/dist/providers/langchain.d.ts +18 -0
- package/dist/providers/langchain.js +23 -0
- package/dist/providers/vercel-ai.d.ts +16 -0
- package/dist/providers/vercel-ai.js +83 -0
- package/dist/sdk/config.d.ts +48 -0
- package/dist/sdk/config.js +10 -0
- package/dist/sdk/index.d.ts +94 -0
- package/dist/sdk/index.js +33 -0
- package/dist/sdk/models.d.ts +40 -0
- package/dist/sdk/models.js +8 -0
- package/dist/sdk/provider.d.ts +21 -0
- package/dist/sdk/provider.js +10 -0
- package/dist/tools.d.ts +20 -0
- package/dist/tools.js +12 -0
- package/dist/types-NtgB3pch.d.ts +92 -0
- package/package.json +8 -13
- package/src/__tests__/cost-observability.test.ts +1 -1
- package/src/__tests__/generation.test.ts +111 -0
- package/src/__tests__/runtime-gateway.test.ts +108 -0
- package/src/gateway.ts +234 -0
- package/src/generation/index.ts +157 -0
- package/src/generation/mock-provider.ts +123 -0
- package/src/generation/types.ts +87 -0
- package/src/index.ts +18 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BUILT_IN_TOOLS,
|
|
3
|
+
databaseQueryTool,
|
|
4
|
+
knowledgeBaseTool,
|
|
5
|
+
webSearchTool
|
|
6
|
+
} from "./chunk-UVL2UVVM.js";
|
|
7
|
+
import {
|
|
8
|
+
buildTelemetryConfig,
|
|
9
|
+
flushTelemetry,
|
|
10
|
+
initLangfuse
|
|
11
|
+
} from "./chunk-B7XWL35G.js";
|
|
12
|
+
import {
|
|
13
|
+
BaseAgent,
|
|
14
|
+
clearMemory,
|
|
15
|
+
getMemory,
|
|
16
|
+
saveMemory
|
|
17
|
+
} from "./chunk-RDOFKRI6.js";
|
|
18
|
+
import {
|
|
19
|
+
configure,
|
|
20
|
+
embed,
|
|
21
|
+
embedMany,
|
|
22
|
+
generateText,
|
|
23
|
+
getConfig,
|
|
24
|
+
streamText
|
|
25
|
+
} from "./chunk-NPQECBXL.js";
|
|
26
|
+
import {
|
|
27
|
+
AgentsEnvSchema,
|
|
28
|
+
buildSystemWithCache,
|
|
29
|
+
filterAvailableProviders,
|
|
30
|
+
getAgentsEnv,
|
|
31
|
+
isLangfuseConfigured,
|
|
32
|
+
isRetryableError,
|
|
33
|
+
runEmbedWithFallback,
|
|
34
|
+
runWithFallback,
|
|
35
|
+
withAnthropicCacheControl
|
|
36
|
+
} from "./chunk-5LX742GP.js";
|
|
37
|
+
import {
|
|
38
|
+
createEmbeddingModel,
|
|
39
|
+
createModel
|
|
40
|
+
} from "./chunk-RLWM437Q.js";
|
|
41
|
+
import {
|
|
42
|
+
NebutraAIConfigSchema
|
|
43
|
+
} from "./chunk-NVPE5EDI.js";
|
|
44
|
+
import {
|
|
45
|
+
models,
|
|
46
|
+
resolveModel
|
|
47
|
+
} from "./chunk-5JZJ5KMC.js";
|
|
48
|
+
|
|
49
|
+
// src/context.ts
|
|
50
|
+
var MAX_BIO_CHARS = 2e3;
|
|
51
|
+
var MAX_INSTRUCTIONS_CHARS = 3e3;
|
|
52
|
+
var MAX_OCCUPATION_CHARS = 120;
|
|
53
|
+
var MAX_NICKNAME_CHARS = 80;
|
|
54
|
+
function present(value) {
|
|
55
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
56
|
+
}
|
|
57
|
+
function clamp(value, max) {
|
|
58
|
+
return value.length > max ? `${value.slice(0, max)}\u2026` : value;
|
|
59
|
+
}
|
|
60
|
+
function renderUserContextBlock(context) {
|
|
61
|
+
if (!context) return null;
|
|
62
|
+
const lines = [];
|
|
63
|
+
if (present(context.nickname)) {
|
|
64
|
+
lines.push(`- Preferred name: ${clamp(context.nickname.trim(), MAX_NICKNAME_CHARS)}`);
|
|
65
|
+
}
|
|
66
|
+
if (present(context.occupation)) {
|
|
67
|
+
lines.push(`- Role: ${clamp(context.occupation.trim(), MAX_OCCUPATION_CHARS)}`);
|
|
68
|
+
}
|
|
69
|
+
if (present(context.bio)) {
|
|
70
|
+
lines.push(`- About them: ${clamp(context.bio.trim(), MAX_BIO_CHARS)}`);
|
|
71
|
+
}
|
|
72
|
+
let block = "";
|
|
73
|
+
if (lines.length > 0) {
|
|
74
|
+
block += `About the user:
|
|
75
|
+
${lines.join("\n")}`;
|
|
76
|
+
}
|
|
77
|
+
if (present(context.customInstructions)) {
|
|
78
|
+
if (block) block += "\n\n";
|
|
79
|
+
block += `The user's custom instructions (these take precedence over defaults):
|
|
80
|
+
${clamp(
|
|
81
|
+
context.customInstructions.trim(),
|
|
82
|
+
MAX_INSTRUCTIONS_CHARS
|
|
83
|
+
)}`;
|
|
84
|
+
}
|
|
85
|
+
return block.length > 0 ? block : null;
|
|
86
|
+
}
|
|
87
|
+
function buildPersonalizedSystemPrompt(basePrompt, context) {
|
|
88
|
+
const block = renderUserContextBlock(context);
|
|
89
|
+
if (!block) return basePrompt;
|
|
90
|
+
return `${block}
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
${basePrompt}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/generation/index.ts
|
|
98
|
+
import { logger } from "@nebutra/logger";
|
|
99
|
+
|
|
100
|
+
// src/generation/mock-provider.ts
|
|
101
|
+
function hash(input) {
|
|
102
|
+
let h = 2166136261;
|
|
103
|
+
for (let i = 0; i < input.length; i++) {
|
|
104
|
+
h ^= input.charCodeAt(i);
|
|
105
|
+
h = Math.imul(h, 16777619);
|
|
106
|
+
}
|
|
107
|
+
return h >>> 0;
|
|
108
|
+
}
|
|
109
|
+
function escapeXml(s) {
|
|
110
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
111
|
+
}
|
|
112
|
+
function svgDataUri(label, prompt, w, h) {
|
|
113
|
+
const hue = hash(prompt) % 360;
|
|
114
|
+
const hue2 = (hue + 40) % 360;
|
|
115
|
+
const words = prompt.split(/\s+/);
|
|
116
|
+
const lines = [];
|
|
117
|
+
let cur = "";
|
|
118
|
+
for (const word of words) {
|
|
119
|
+
if ((cur + " " + word).trim().length > 32) {
|
|
120
|
+
lines.push(cur.trim());
|
|
121
|
+
cur = word;
|
|
122
|
+
} else {
|
|
123
|
+
cur = `${cur} ${word}`;
|
|
124
|
+
}
|
|
125
|
+
if (lines.length === 4) break;
|
|
126
|
+
}
|
|
127
|
+
if (cur && lines.length < 4) lines.push(cur.trim());
|
|
128
|
+
const tspans = lines.map((ln, i) => `<tspan x="50%" dy="${i === 0 ? 0 : 26}">${escapeXml(ln)}</tspan>`).join("");
|
|
129
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
|
|
130
|
+
<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
131
|
+
<stop offset="0" stop-color="hsl(${hue} 70% 55%)"/>
|
|
132
|
+
<stop offset="1" stop-color="hsl(${hue2} 70% 45%)"/>
|
|
133
|
+
</linearGradient></defs>
|
|
134
|
+
<rect width="${w}" height="${h}" fill="url(#g)"/>
|
|
135
|
+
<text x="50%" y="14%" fill="rgba(255,255,255,.7)" font-family="sans-serif" font-size="20" text-anchor="middle">${escapeXml(label)}</text>
|
|
136
|
+
<text x="50%" y="46%" fill="#fff" font-family="sans-serif" font-size="22" font-weight="600" text-anchor="middle">${tspans}</text>
|
|
137
|
+
</svg>`;
|
|
138
|
+
const b64 = typeof btoa === "function" ? btoa(unescape(encodeURIComponent(svg))) : Buffer.from(svg, "utf8").toString("base64");
|
|
139
|
+
return `data:image/svg+xml;base64,${b64}`;
|
|
140
|
+
}
|
|
141
|
+
var mockGenerationProvider = {
|
|
142
|
+
name: "mock",
|
|
143
|
+
envKey: null,
|
|
144
|
+
capabilities: ["image", "video"],
|
|
145
|
+
async generateImage(req, _ctx) {
|
|
146
|
+
const width = req.width ?? 1024;
|
|
147
|
+
const height = req.height ?? 1024;
|
|
148
|
+
return {
|
|
149
|
+
modality: "image",
|
|
150
|
+
mimeType: "image/svg+xml",
|
|
151
|
+
url: svgDataUri("mock \xB7 image", req.prompt, width, height),
|
|
152
|
+
width,
|
|
153
|
+
height,
|
|
154
|
+
providerName: "mock",
|
|
155
|
+
model: req.model ?? "mock-image-1",
|
|
156
|
+
usage: { units: 1 }
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
async generateVideo(req, _ctx) {
|
|
160
|
+
const width = req.width ?? 1280;
|
|
161
|
+
const height = req.height ?? 720;
|
|
162
|
+
const seconds = req.durationSeconds ?? 5;
|
|
163
|
+
return {
|
|
164
|
+
modality: "video",
|
|
165
|
+
mimeType: "image/svg+xml",
|
|
166
|
+
url: svgDataUri(`mock \xB7 video \xB7 ${seconds}s`, req.prompt, width, height),
|
|
167
|
+
width,
|
|
168
|
+
height,
|
|
169
|
+
providerName: "mock",
|
|
170
|
+
model: req.model ?? "mock-video-1",
|
|
171
|
+
usage: { units: seconds }
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// src/generation/index.ts
|
|
177
|
+
var log = logger.child({ module: "agents/generation" });
|
|
178
|
+
var _registry = /* @__PURE__ */ new Map();
|
|
179
|
+
function registerGenerationProvider(provider) {
|
|
180
|
+
_registry.set(provider.name, provider);
|
|
181
|
+
}
|
|
182
|
+
function _resetGenerationRegistry() {
|
|
183
|
+
_registry.clear();
|
|
184
|
+
_registry.set(mockGenerationProvider.name, mockGenerationProvider);
|
|
185
|
+
}
|
|
186
|
+
_resetGenerationRegistry();
|
|
187
|
+
function hasEnvKey(provider) {
|
|
188
|
+
if (provider.envKey === null) return true;
|
|
189
|
+
return Boolean(globalThis.process?.env?.[provider.envKey]);
|
|
190
|
+
}
|
|
191
|
+
function listGenerationProviders(modality, options = {}) {
|
|
192
|
+
const envChain = (globalThis.process?.env?.GENERATION_FALLBACK_CHAIN ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
193
|
+
const preferred = options.chain ?? (envChain.length > 0 ? envChain : []);
|
|
194
|
+
const mockName = mockGenerationProvider.name;
|
|
195
|
+
const all = [...preferred, ..._registry.keys()];
|
|
196
|
+
const seen = /* @__PURE__ */ new Set();
|
|
197
|
+
const resolved = [];
|
|
198
|
+
for (const name of all) {
|
|
199
|
+
if (seen.has(name) || name === mockName) continue;
|
|
200
|
+
seen.add(name);
|
|
201
|
+
const provider = _registry.get(name);
|
|
202
|
+
if (!provider) continue;
|
|
203
|
+
if (!provider.capabilities.includes(modality)) continue;
|
|
204
|
+
if (!hasEnvKey(provider)) continue;
|
|
205
|
+
resolved.push(name);
|
|
206
|
+
}
|
|
207
|
+
resolved.push(mockName);
|
|
208
|
+
return resolved;
|
|
209
|
+
}
|
|
210
|
+
async function runChain(modality, options, ctx, invoke) {
|
|
211
|
+
const chain = listGenerationProviders(modality, options);
|
|
212
|
+
let lastErr;
|
|
213
|
+
for (const name of chain) {
|
|
214
|
+
const provider = _registry.get(name);
|
|
215
|
+
if (!provider) continue;
|
|
216
|
+
try {
|
|
217
|
+
const result = await invoke(provider);
|
|
218
|
+
log.debug("generation succeeded", {
|
|
219
|
+
provider: name,
|
|
220
|
+
modality,
|
|
221
|
+
tenantId: ctx.tenantId
|
|
222
|
+
});
|
|
223
|
+
return result;
|
|
224
|
+
} catch (err) {
|
|
225
|
+
lastErr = err;
|
|
226
|
+
const retryable = isRetryableError(err);
|
|
227
|
+
log.warn("generation provider failed", {
|
|
228
|
+
provider: name,
|
|
229
|
+
modality,
|
|
230
|
+
retryable,
|
|
231
|
+
tenantId: ctx.tenantId
|
|
232
|
+
});
|
|
233
|
+
if (!retryable && name !== mockGenerationProvider.name) continue;
|
|
234
|
+
if (!retryable) throw err;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
throw lastErr instanceof Error ? lastErr : new Error("[@nebutra/agents] generation chain exhausted");
|
|
238
|
+
}
|
|
239
|
+
async function generateImage(req, ctx, options = {}) {
|
|
240
|
+
return runChain("image", options, ctx, (p) => {
|
|
241
|
+
if (!p.generateImage) {
|
|
242
|
+
throw new Error(`[@nebutra/agents] provider "${p.name}" lacks image support`);
|
|
243
|
+
}
|
|
244
|
+
return p.generateImage(req, ctx);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
async function generateVideo(req, ctx, options = {}) {
|
|
248
|
+
return runChain("video", options, ctx, (p) => {
|
|
249
|
+
if (!p.generateVideo) {
|
|
250
|
+
throw new Error(`[@nebutra/agents] provider "${p.name}" lacks video support`);
|
|
251
|
+
}
|
|
252
|
+
return p.generateVideo(req, ctx);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/orchestrator.ts
|
|
257
|
+
import { logger as logger3 } from "@nebutra/logger";
|
|
258
|
+
|
|
259
|
+
// src/router.ts
|
|
260
|
+
import { logger as logger2 } from "@nebutra/logger";
|
|
261
|
+
var AgentRouter = class {
|
|
262
|
+
config;
|
|
263
|
+
constructor(config) {
|
|
264
|
+
this.config = config;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Route a message to the best-matching agent.
|
|
268
|
+
* Returns the agent ID.
|
|
269
|
+
*/
|
|
270
|
+
async route(message, agents, context, defaultAgentId) {
|
|
271
|
+
if (agents.length === 0) {
|
|
272
|
+
throw new Error("No agents configured in the orchestrator");
|
|
273
|
+
}
|
|
274
|
+
const fallback = defaultAgentId ?? agents[0]?.id;
|
|
275
|
+
if (!fallback) {
|
|
276
|
+
throw new Error("No agents configured in the orchestrator");
|
|
277
|
+
}
|
|
278
|
+
switch (this.config.strategy) {
|
|
279
|
+
case "keyword":
|
|
280
|
+
return this.routeByKeyword(message, agents, defaultAgentId) ?? fallback;
|
|
281
|
+
case "llm":
|
|
282
|
+
return await this.routeByLLM(message, agents, defaultAgentId) ?? fallback;
|
|
283
|
+
case "custom":
|
|
284
|
+
return this.routeByCustom(message, context, defaultAgentId);
|
|
285
|
+
default: {
|
|
286
|
+
const _exhaustive = this.config.strategy;
|
|
287
|
+
throw new Error(`Unknown routing strategy: ${String(_exhaustive)}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Keyword-based routing: score each agent by how many words from
|
|
293
|
+
* its name + description appear in the user message.
|
|
294
|
+
*/
|
|
295
|
+
routeByKeyword(message, agents, defaultAgentId) {
|
|
296
|
+
const messageLower = message.toLowerCase();
|
|
297
|
+
let bestId = defaultAgentId ?? agents[0]?.id;
|
|
298
|
+
let bestScore = 0;
|
|
299
|
+
for (const agent of agents) {
|
|
300
|
+
const keywords = `${agent.name} ${agent.description}`.toLowerCase().split(/\s+/);
|
|
301
|
+
let score = 0;
|
|
302
|
+
for (const keyword of keywords) {
|
|
303
|
+
if (keyword.length > 2 && messageLower.includes(keyword)) {
|
|
304
|
+
score += 1;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (score > bestScore) {
|
|
308
|
+
bestScore = score;
|
|
309
|
+
bestId = agent.id;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
logger2.info("Router: keyword match", {
|
|
313
|
+
selectedAgentId: bestId,
|
|
314
|
+
score: bestScore
|
|
315
|
+
});
|
|
316
|
+
return bestId;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* LLM-based routing: uses a small model to classify the user's intent
|
|
320
|
+
* and select the best agent. Falls back to keyword if AI SDK unavailable.
|
|
321
|
+
*/
|
|
322
|
+
async routeByLLM(message, agents, defaultAgentId) {
|
|
323
|
+
try {
|
|
324
|
+
const { generateText: generateText2 } = await import("ai");
|
|
325
|
+
const agentList = agents.map((a) => `- ${a.id}: ${a.description}`).join("\n");
|
|
326
|
+
const result = await generateText2({
|
|
327
|
+
model: agents[0]?.model,
|
|
328
|
+
system: [
|
|
329
|
+
"You are a routing classifier. Given a user message and a list of agents,",
|
|
330
|
+
"respond with ONLY the agent ID that best matches the user's intent.",
|
|
331
|
+
"If no agent is a good match, respond with the default agent ID.",
|
|
332
|
+
"",
|
|
333
|
+
`Available agents:
|
|
334
|
+
${agentList}`,
|
|
335
|
+
"",
|
|
336
|
+
defaultAgentId ? `Default agent: ${defaultAgentId}` : ""
|
|
337
|
+
].join("\n"),
|
|
338
|
+
messages: [{ role: "user", content: message }]
|
|
339
|
+
});
|
|
340
|
+
const selectedId = result.text.trim();
|
|
341
|
+
const validAgent = agents.find((a) => a.id === selectedId);
|
|
342
|
+
if (validAgent) {
|
|
343
|
+
logger2.info("Router: LLM classification", {
|
|
344
|
+
selectedAgentId: validAgent.id
|
|
345
|
+
});
|
|
346
|
+
return validAgent.id;
|
|
347
|
+
}
|
|
348
|
+
return defaultAgentId ?? agents[0]?.id;
|
|
349
|
+
} catch (error) {
|
|
350
|
+
logger2.warn("Router: LLM routing failed, falling back to keyword", {
|
|
351
|
+
error
|
|
352
|
+
});
|
|
353
|
+
return this.routeByKeyword(message, agents, defaultAgentId);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Custom routing via user-supplied function.
|
|
358
|
+
*/
|
|
359
|
+
async routeByCustom(message, context, defaultAgentId) {
|
|
360
|
+
if (!this.config.customRouter) {
|
|
361
|
+
throw new Error(
|
|
362
|
+
'RouterConfig strategy is "custom" but no customRouter function was provided'
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
return await this.config.customRouter(message, context);
|
|
367
|
+
} catch (error) {
|
|
368
|
+
logger2.warn("Router: custom router failed", { error });
|
|
369
|
+
if (defaultAgentId) return defaultAgentId;
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// src/tenant.ts
|
|
376
|
+
function createAgentContext(tenantId, userId, conversationId, metadata) {
|
|
377
|
+
return {
|
|
378
|
+
tenantId,
|
|
379
|
+
userId,
|
|
380
|
+
conversationId: conversationId ?? crypto.randomUUID(),
|
|
381
|
+
...metadata !== void 0 ? { metadata } : {}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
async function checkAgentQuota(tenantId) {
|
|
385
|
+
try {
|
|
386
|
+
const { getCreditBalance } = await import("@nebutra/billing/credits");
|
|
387
|
+
const balance = await getCreditBalance(tenantId);
|
|
388
|
+
if (balance.balance > 0) {
|
|
389
|
+
return { allowed: true, remaining: balance.balance };
|
|
390
|
+
}
|
|
391
|
+
return { allowed: false, remaining: 0 };
|
|
392
|
+
} catch (_err) {
|
|
393
|
+
return { allowed: true, remaining: -1 };
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/orchestrator.ts
|
|
398
|
+
var AgentOrchestrator = class {
|
|
399
|
+
agents;
|
|
400
|
+
router;
|
|
401
|
+
defaultAgentId;
|
|
402
|
+
constructor(config) {
|
|
403
|
+
this.agents = /* @__PURE__ */ new Map();
|
|
404
|
+
this.defaultAgentId = config.defaultAgentId;
|
|
405
|
+
for (const agentConfig of config.agents) {
|
|
406
|
+
this.agents.set(agentConfig.id, new BaseAgent(agentConfig));
|
|
407
|
+
}
|
|
408
|
+
this.router = new AgentRouter(config.router ?? { strategy: "keyword" });
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Register a pre-built agent instance (e.g. VercelAIAgent).
|
|
412
|
+
* Overwrites any agent with the same ID.
|
|
413
|
+
*/
|
|
414
|
+
registerAgent(agent) {
|
|
415
|
+
this.agents.set(agent.config.id, agent);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Get a registered agent by ID.
|
|
419
|
+
*/
|
|
420
|
+
getAgent(agentId) {
|
|
421
|
+
return this.agents.get(agentId);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Route a message to the best agent and execute.
|
|
425
|
+
*/
|
|
426
|
+
async chat(message, context) {
|
|
427
|
+
await this.assertQuota(context.tenantId);
|
|
428
|
+
const agentConfigs = [...this.agents.values()].map((a) => a.config);
|
|
429
|
+
const agentId = await this.router.route(message, agentConfigs, context, this.defaultAgentId);
|
|
430
|
+
const agent = this.agents.get(agentId);
|
|
431
|
+
if (!agent) {
|
|
432
|
+
throw new Error(`Agent "${agentId}" not found in orchestrator`);
|
|
433
|
+
}
|
|
434
|
+
const messages = [{ role: "user", content: message, timestamp: /* @__PURE__ */ new Date() }];
|
|
435
|
+
return agent.run(messages, context);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Execute a multi-agent pipeline where each step's output feeds the next.
|
|
439
|
+
* @experimental — API may change. Use `chat()` for production workloads.
|
|
440
|
+
*/
|
|
441
|
+
async pipeline(steps, input, context) {
|
|
442
|
+
await this.assertQuota(context.tenantId);
|
|
443
|
+
let currentInput = input;
|
|
444
|
+
let lastResponse;
|
|
445
|
+
for (const step of steps) {
|
|
446
|
+
const agent = this.agents.get(step.agentId);
|
|
447
|
+
if (!agent) {
|
|
448
|
+
throw new Error(`Pipeline step references unknown agent "${step.agentId}"`);
|
|
449
|
+
}
|
|
450
|
+
const transformedInput = step.transformInput ? step.transformInput(currentInput) : currentInput;
|
|
451
|
+
const messages = [
|
|
452
|
+
{ role: "user", content: transformedInput, timestamp: /* @__PURE__ */ new Date() }
|
|
453
|
+
];
|
|
454
|
+
lastResponse = await agent.run(messages, context);
|
|
455
|
+
const assistantMessages = lastResponse.messages.filter((m) => m.role === "assistant");
|
|
456
|
+
const lastAssistant = assistantMessages[assistantMessages.length - 1];
|
|
457
|
+
currentInput = lastAssistant?.content ?? "";
|
|
458
|
+
logger3.info("Pipeline step completed", {
|
|
459
|
+
agentId: step.agentId,
|
|
460
|
+
tenantId: context.tenantId
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
if (!lastResponse) {
|
|
464
|
+
throw new Error("Pipeline produced no response (empty steps?)");
|
|
465
|
+
}
|
|
466
|
+
return lastResponse;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Broadcast a message to ALL registered agents in parallel.
|
|
470
|
+
* Returns an array of responses (one per agent).
|
|
471
|
+
* @experimental — API may change. Use `chat()` for production workloads.
|
|
472
|
+
*/
|
|
473
|
+
async broadcast(message, context) {
|
|
474
|
+
await this.assertQuota(context.tenantId);
|
|
475
|
+
const messages = [{ role: "user", content: message, timestamp: /* @__PURE__ */ new Date() }];
|
|
476
|
+
const promises = [...this.agents.values()].map((agent) => agent.run(messages, context));
|
|
477
|
+
return Promise.all(promises);
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Check tenant quota before execution.
|
|
481
|
+
*/
|
|
482
|
+
async assertQuota(tenantId) {
|
|
483
|
+
const { allowed } = await checkAgentQuota(tenantId);
|
|
484
|
+
if (!allowed) {
|
|
485
|
+
throw new Error(`Tenant "${tenantId}" has exceeded agent execution quota`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
export {
|
|
490
|
+
AgentOrchestrator,
|
|
491
|
+
AgentRouter,
|
|
492
|
+
AgentsEnvSchema,
|
|
493
|
+
BUILT_IN_TOOLS,
|
|
494
|
+
BaseAgent,
|
|
495
|
+
NebutraAIConfigSchema,
|
|
496
|
+
_resetGenerationRegistry,
|
|
497
|
+
buildPersonalizedSystemPrompt,
|
|
498
|
+
buildSystemWithCache,
|
|
499
|
+
buildTelemetryConfig,
|
|
500
|
+
checkAgentQuota,
|
|
501
|
+
clearMemory,
|
|
502
|
+
configure,
|
|
503
|
+
createAgentContext,
|
|
504
|
+
createEmbeddingModel,
|
|
505
|
+
createModel,
|
|
506
|
+
databaseQueryTool,
|
|
507
|
+
embed,
|
|
508
|
+
embedMany,
|
|
509
|
+
filterAvailableProviders,
|
|
510
|
+
flushTelemetry,
|
|
511
|
+
generateImage,
|
|
512
|
+
generateText,
|
|
513
|
+
generateVideo,
|
|
514
|
+
getAgentsEnv,
|
|
515
|
+
getConfig,
|
|
516
|
+
getMemory,
|
|
517
|
+
initLangfuse,
|
|
518
|
+
isLangfuseConfigured,
|
|
519
|
+
isRetryableError,
|
|
520
|
+
knowledgeBaseTool,
|
|
521
|
+
listGenerationProviders,
|
|
522
|
+
mockGenerationProvider,
|
|
523
|
+
models,
|
|
524
|
+
registerGenerationProvider,
|
|
525
|
+
renderUserContextBlock,
|
|
526
|
+
resolveModel,
|
|
527
|
+
runEmbedWithFallback,
|
|
528
|
+
runWithFallback,
|
|
529
|
+
saveMemory,
|
|
530
|
+
streamText,
|
|
531
|
+
webSearchTool,
|
|
532
|
+
withAnthropicCacheControl
|
|
533
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { B as BaseAgent } from '../agent-CahnMASx.js';
|
|
2
|
+
import { c as AgentConfig } from '../types-NtgB3pch.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* LangChain.js agent adapter — stub.
|
|
6
|
+
*
|
|
7
|
+
* This module intentionally throws at construction time to guide
|
|
8
|
+
* developers through the required dependency installation.
|
|
9
|
+
*
|
|
10
|
+
* Once the packages are installed, implement the `execute()` method
|
|
11
|
+
* using LangChain's AgentExecutor or the newer LangGraph approach.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
declare class LangChainAgent extends BaseAgent {
|
|
15
|
+
constructor(config: AgentConfig);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { LangChainAgent };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseAgent
|
|
3
|
+
} from "../chunk-RDOFKRI6.js";
|
|
4
|
+
|
|
5
|
+
// src/providers/langchain.ts
|
|
6
|
+
var LangChainAgent = class extends BaseAgent {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
super(config);
|
|
9
|
+
throw new Error(
|
|
10
|
+
[
|
|
11
|
+
"LangChain agent requires additional packages. Install:",
|
|
12
|
+
"",
|
|
13
|
+
" pnpm add langchain @langchain/core @langchain/openai",
|
|
14
|
+
"",
|
|
15
|
+
"Then implement the execute() method in this file using",
|
|
16
|
+
"LangChain's AgentExecutor or LangGraph."
|
|
17
|
+
].join("\n")
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
export {
|
|
22
|
+
LangChainAgent
|
|
23
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { B as BaseAgent } from '../agent-CahnMASx.js';
|
|
2
|
+
import { A as AgentMessage, a as AgentContext, b as AgentResponse } from '../types-NtgB3pch.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Vercel AI SDK agent adapter.
|
|
6
|
+
*
|
|
7
|
+
* Uses `streamText` from the `ai` package with tool-loop support.
|
|
8
|
+
* The `ai` peer dependency is dynamically imported so the package
|
|
9
|
+
* doesn't fail at require-time when the SDK is absent.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
declare class VercelAIAgent extends BaseAgent {
|
|
13
|
+
protected execute(messages: readonly AgentMessage[], context: AgentContext): Promise<AgentResponse>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { VercelAIAgent };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildTelemetryConfig
|
|
3
|
+
} from "../chunk-B7XWL35G.js";
|
|
4
|
+
import {
|
|
5
|
+
BaseAgent
|
|
6
|
+
} from "../chunk-RDOFKRI6.js";
|
|
7
|
+
import {
|
|
8
|
+
runWithFallback,
|
|
9
|
+
withAnthropicCacheControl
|
|
10
|
+
} from "../chunk-5LX742GP.js";
|
|
11
|
+
import "../chunk-5JZJ5KMC.js";
|
|
12
|
+
|
|
13
|
+
// src/providers/vercel-ai.ts
|
|
14
|
+
var VercelAIAgent = class extends BaseAgent {
|
|
15
|
+
async execute(messages, context) {
|
|
16
|
+
const { streamText, stepCountIs, dynamicTool } = await import("ai");
|
|
17
|
+
const toolSet = this.config.tools ? Object.fromEntries(
|
|
18
|
+
this.config.tools.map((t) => [
|
|
19
|
+
t.name,
|
|
20
|
+
dynamicTool({
|
|
21
|
+
description: t.description,
|
|
22
|
+
inputSchema: t.inputSchema,
|
|
23
|
+
execute: async (args) => t.execute(args, context)
|
|
24
|
+
})
|
|
25
|
+
])
|
|
26
|
+
) : void 0;
|
|
27
|
+
const telemetry = buildTelemetryConfig({
|
|
28
|
+
functionId: `agent.${this.config.id}`,
|
|
29
|
+
metadata: {
|
|
30
|
+
tenantId: context.tenantId,
|
|
31
|
+
userId: context.userId,
|
|
32
|
+
sessionId: context.conversationId,
|
|
33
|
+
agentId: this.config.id
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const baseOptionsWithoutModel = {
|
|
37
|
+
system: this.config.instructions,
|
|
38
|
+
messages: messages.map((m) => ({
|
|
39
|
+
role: m.role,
|
|
40
|
+
content: m.content
|
|
41
|
+
})),
|
|
42
|
+
stopWhen: stepCountIs(this.config.maxSteps ?? 20),
|
|
43
|
+
// Anthropic prompt cache control on the system message — 90% cost
|
|
44
|
+
// reduction on cached prefix tokens. No-op for non-Anthropic providers.
|
|
45
|
+
providerOptions: withAnthropicCacheControl(),
|
|
46
|
+
experimental_telemetry: telemetry
|
|
47
|
+
};
|
|
48
|
+
const { result } = await runWithFallback(
|
|
49
|
+
async (model) => {
|
|
50
|
+
const streamOptions = {
|
|
51
|
+
...baseOptionsWithoutModel,
|
|
52
|
+
model,
|
|
53
|
+
...toolSet !== void 0 ? { tools: toolSet } : {}
|
|
54
|
+
};
|
|
55
|
+
const r = streamText(streamOptions);
|
|
56
|
+
const text2 = await r.text;
|
|
57
|
+
const usage2 = await r.usage;
|
|
58
|
+
return { text: text2, usage: usage2 };
|
|
59
|
+
},
|
|
60
|
+
{ model: this.config.model }
|
|
61
|
+
);
|
|
62
|
+
const { text, usage } = result;
|
|
63
|
+
const inputTokens = usage?.inputTokens ?? 0;
|
|
64
|
+
const outputTokens = usage?.outputTokens ?? 0;
|
|
65
|
+
const responseMessages = [
|
|
66
|
+
...messages,
|
|
67
|
+
{ role: "assistant", content: text, timestamp: /* @__PURE__ */ new Date() }
|
|
68
|
+
];
|
|
69
|
+
return {
|
|
70
|
+
messages: responseMessages,
|
|
71
|
+
usage: {
|
|
72
|
+
promptTokens: inputTokens,
|
|
73
|
+
completionTokens: outputTokens,
|
|
74
|
+
totalTokens: inputTokens + outputTokens
|
|
75
|
+
},
|
|
76
|
+
finishReason: "stop",
|
|
77
|
+
agentId: this.config.id
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
export {
|
|
82
|
+
VercelAIAgent
|
|
83
|
+
};
|