@nebutra/agents 1.1.1 → 1.1.2
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 -676
- package/README.md +7 -3
- package/dist/{agent-CahnMASx.d.ts → agent-DDGWuUpe.d.ts} +1 -1
- package/{src/env.ts → dist/chunk-BMSL4E4A.js} +23 -47
- package/dist/{chunk-5JZJ5KMC.js → chunk-BZKIMAGK.js} +16 -7
- package/dist/{chunk-NPQECBXL.js → chunk-GRSMUTUS.js} +60 -7
- package/dist/{chunk-B7XWL35G.js → chunk-QYZDHC5A.js} +5 -1
- package/dist/{chunk-RLWM437Q.js → chunk-RNFUEMUB.js} +10 -3
- package/dist/chunk-RWQL4HXC.js +171 -0
- package/dist/{chunk-NVPE5EDI.js → chunk-V6VC2O6Q.js} +4 -3
- package/dist/chunk-VZPQOXWW.js +47 -0
- package/dist/{chunk-5LX742GP.js → chunk-XLBS3XUI.js} +4 -50
- package/dist/env.d.ts +42 -0
- package/dist/env.js +12 -0
- package/dist/fallback.d.ts +100 -0
- package/dist/fallback.js +18 -0
- package/dist/generation/index.d.ts +122 -0
- package/dist/generation/index.js +19 -0
- package/dist/index.d.ts +21 -328
- package/dist/index.js +57 -245
- package/dist/observability.d.ts +46 -0
- package/dist/observability.js +13 -0
- package/dist/providers/langchain.d.ts +2 -2
- package/dist/providers/vercel-ai.d.ts +2 -2
- package/dist/providers/vercel-ai.js +15 -7
- package/dist/sdk/config.d.ts +3 -0
- package/dist/sdk/config.js +1 -1
- package/dist/sdk/index.d.ts +36 -3
- package/dist/sdk/index.js +20 -7
- package/dist/sdk/models.d.ts +18 -6
- package/dist/sdk/models.js +1 -1
- package/dist/sdk/provider.d.ts +1 -0
- package/dist/sdk/provider.js +3 -3
- package/dist/tools.d.ts +1 -1
- package/dist/{types-NtgB3pch.d.ts → types-BytC-HfQ.d.ts} +1 -5
- package/package.json +71 -19
- package/.turbo/turbo-build.log +0 -40
- package/.turbo/turbo-test.log +0 -19
- package/.turbo/turbo-typecheck.log +0 -4
- package/AGENTS.md +0 -63
- package/CHANGELOG.md +0 -36
- package/src/__tests__/cost-observability.test.ts +0 -172
- package/src/__tests__/fallback-wiring.test.ts +0 -313
- package/src/__tests__/generation.test.ts +0 -111
- package/src/__tests__/public-api.test.ts +0 -114
- package/src/__tests__/runtime-gateway.test.ts +0 -108
- package/src/agent.ts +0 -117
- package/src/context.ts +0 -99
- package/src/fallback.ts +0 -358
- package/src/gateway.ts +0 -234
- package/src/generation/index.ts +0 -157
- package/src/generation/mock-provider.ts +0 -123
- package/src/generation/types.ts +0 -87
- package/src/index.ts +0 -104
- package/src/memory.ts +0 -126
- package/src/observability.ts +0 -102
- package/src/orchestrator.ts +0 -147
- package/src/providers/langchain.ts +0 -28
- package/src/providers/vercel-ai.ts +0 -114
- package/src/router.ts +0 -158
- package/src/sdk/config.ts +0 -73
- package/src/sdk/index.ts +0 -214
- package/src/sdk/models.ts +0 -57
- package/src/sdk/provider.ts +0 -80
- package/src/tenant.ts +0 -52
- package/src/tools.ts +0 -65
- package/src/types.ts +0 -114
- package/tsconfig.json +0 -12
- package/tsup.config.ts +0 -21
package/src/index.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
// ─── Core ─────────────────────────────────────────────────────────────────────
|
|
2
|
-
export { BaseAgent } from "./agent";
|
|
3
|
-
// ─── User context (personalization) ───────────────────────────────────────────
|
|
4
|
-
export {
|
|
5
|
-
buildPersonalizedSystemPrompt,
|
|
6
|
-
renderUserContextBlock,
|
|
7
|
-
type UserContext,
|
|
8
|
-
} from "./context";
|
|
9
|
-
// ─── Env / Observability / Fallback ─────────────────────────────────────────
|
|
10
|
-
export {
|
|
11
|
-
type AgentsEnv,
|
|
12
|
-
AgentsEnvSchema,
|
|
13
|
-
type FallbackProviderName,
|
|
14
|
-
getAgentsEnv,
|
|
15
|
-
isLangfuseConfigured,
|
|
16
|
-
} from "./env";
|
|
17
|
-
export {
|
|
18
|
-
buildSystemWithCache,
|
|
19
|
-
type CreateFallbackModelOptions,
|
|
20
|
-
type EmbeddingFallbackOptions,
|
|
21
|
-
type FallbackResult,
|
|
22
|
-
filterAvailableProviders,
|
|
23
|
-
isRetryableError,
|
|
24
|
-
runEmbedWithFallback,
|
|
25
|
-
runWithFallback,
|
|
26
|
-
withAnthropicCacheControl,
|
|
27
|
-
} from "./fallback";
|
|
28
|
-
// ─── Generation (image / video modality) ─────────────────────────────────────
|
|
29
|
-
// New modality on the same env-key-gated provider layer as the LLM fallback
|
|
30
|
-
// chain. `mock` is always available so CI / flag-gated demos need no secret.
|
|
31
|
-
export {
|
|
32
|
-
_resetGenerationRegistry,
|
|
33
|
-
type GenerationCallOptions,
|
|
34
|
-
type GenerationContext,
|
|
35
|
-
type GenerationModality,
|
|
36
|
-
type GenerationProvider,
|
|
37
|
-
type GenerationResult,
|
|
38
|
-
generateImage,
|
|
39
|
-
generateVideo,
|
|
40
|
-
type ImageGenerationRequest,
|
|
41
|
-
listGenerationProviders,
|
|
42
|
-
mockGenerationProvider,
|
|
43
|
-
registerGenerationProvider,
|
|
44
|
-
type VideoGenerationRequest,
|
|
45
|
-
} from "./generation/index";
|
|
46
|
-
// ─── Memory ───────────────────────────────────────────────────────────────────
|
|
47
|
-
export { clearMemory, getMemory, saveMemory } from "./memory";
|
|
48
|
-
export {
|
|
49
|
-
buildTelemetryConfig,
|
|
50
|
-
flushTelemetry,
|
|
51
|
-
initLangfuse,
|
|
52
|
-
type TelemetryMetadata,
|
|
53
|
-
} from "./observability";
|
|
54
|
-
export { AgentOrchestrator } from "./orchestrator";
|
|
55
|
-
export { AgentRouter } from "./router";
|
|
56
|
-
// ─── Vercel AI SDK helpers (absorbed from @nebutra/ai-sdk) ───────────────────
|
|
57
|
-
// Top-level generation, streaming and embedding helpers that wrap the Vercel
|
|
58
|
-
// AI SDK (`ai` package) with a single configure()-driven provider resolver.
|
|
59
|
-
export {
|
|
60
|
-
configure,
|
|
61
|
-
createEmbeddingModel,
|
|
62
|
-
createModel,
|
|
63
|
-
type EmbedOptions,
|
|
64
|
-
embed,
|
|
65
|
-
embedMany,
|
|
66
|
-
type GenerateOptions,
|
|
67
|
-
type GenerateTextResult,
|
|
68
|
-
generateText,
|
|
69
|
-
getConfig,
|
|
70
|
-
type ModelMessage,
|
|
71
|
-
type ModelPreset,
|
|
72
|
-
models,
|
|
73
|
-
type NebutraAIConfig,
|
|
74
|
-
NebutraAIConfigSchema,
|
|
75
|
-
type ProviderType,
|
|
76
|
-
type ResolvedNebutraAIConfig,
|
|
77
|
-
resolveModel,
|
|
78
|
-
type StreamTextResult,
|
|
79
|
-
streamText,
|
|
80
|
-
} from "./sdk/index";
|
|
81
|
-
// ─── Tenant ───────────────────────────────────────────────────────────────────
|
|
82
|
-
export { checkAgentQuota, createAgentContext } from "./tenant";
|
|
83
|
-
// ─── Tools ────────────────────────────────────────────────────────────────────
|
|
84
|
-
export {
|
|
85
|
-
BUILT_IN_TOOLS,
|
|
86
|
-
databaseQueryTool,
|
|
87
|
-
knowledgeBaseTool,
|
|
88
|
-
webSearchTool,
|
|
89
|
-
} from "./tools";
|
|
90
|
-
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
91
|
-
export type {
|
|
92
|
-
AgentConfig,
|
|
93
|
-
AgentContext,
|
|
94
|
-
AgentMessage,
|
|
95
|
-
AgentResponse,
|
|
96
|
-
AgentTool,
|
|
97
|
-
AgentUsageEvent,
|
|
98
|
-
MemoryConfig,
|
|
99
|
-
OrchestratorConfig,
|
|
100
|
-
PipelineStep,
|
|
101
|
-
RouterConfig,
|
|
102
|
-
TokenUsage,
|
|
103
|
-
ToolCallResult,
|
|
104
|
-
} from "./types";
|
package/src/memory.ts
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent memory — Redis-backed per-tenant conversation persistence.
|
|
3
|
-
*
|
|
4
|
-
* Key format: `agent:memory:{tenantId}:{conversationId}`
|
|
5
|
-
* TTL: 7 days (configurable via AGENT_MEMORY_TTL_SECONDS env var).
|
|
6
|
-
*
|
|
7
|
-
* Graceful degradation: if Redis is unavailable, functions return
|
|
8
|
-
* empty arrays / silently skip writes so agents still work in
|
|
9
|
-
* in-memory-only mode.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { logger } from "@nebutra/logger";
|
|
13
|
-
import type { AgentMessage } from "./types";
|
|
14
|
-
|
|
15
|
-
const DEFAULT_TTL_SECONDS = 7 * 24 * 60 * 60; // 7 days
|
|
16
|
-
|
|
17
|
-
function getTtl(): number {
|
|
18
|
-
const envTtl = process.env.AGENT_MEMORY_TTL_SECONDS;
|
|
19
|
-
if (envTtl) {
|
|
20
|
-
const parsed = Number.parseInt(envTtl, 10);
|
|
21
|
-
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
22
|
-
return parsed;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return DEFAULT_TTL_SECONDS;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function memoryKey(tenantId: string, conversationId: string): string {
|
|
29
|
-
return `agent:memory:${tenantId}:${conversationId}`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Lazily resolve Redis. Returns null when Redis is not configured
|
|
34
|
-
* so callers can gracefully degrade.
|
|
35
|
-
*/
|
|
36
|
-
async function tryGetRedis() {
|
|
37
|
-
try {
|
|
38
|
-
const { getRedis } = await import("@nebutra/cache");
|
|
39
|
-
return getRedis();
|
|
40
|
-
} catch {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Load conversation history from Redis.
|
|
47
|
-
* Returns an empty array when Redis is unavailable.
|
|
48
|
-
*/
|
|
49
|
-
export async function getMemory(tenantId: string, conversationId: string): Promise<AgentMessage[]> {
|
|
50
|
-
const redis = await tryGetRedis();
|
|
51
|
-
if (!redis) return [];
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const raw = await redis.get<string>(memoryKey(tenantId, conversationId));
|
|
55
|
-
if (!raw) return [];
|
|
56
|
-
|
|
57
|
-
const parsed: unknown = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
58
|
-
if (!Array.isArray(parsed)) return [];
|
|
59
|
-
|
|
60
|
-
return parsed.map((m: Record<string, unknown>): AgentMessage => {
|
|
61
|
-
const toolCalls = m.toolCalls;
|
|
62
|
-
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
63
|
-
return {
|
|
64
|
-
role: m.role as AgentMessage["role"],
|
|
65
|
-
content: String(m.content ?? ""),
|
|
66
|
-
toolCalls: toolCalls as unknown as NonNullable<AgentMessage["toolCalls"]>,
|
|
67
|
-
timestamp: new Date(String(m.timestamp)),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
return {
|
|
71
|
-
role: m.role as AgentMessage["role"],
|
|
72
|
-
content: String(m.content ?? ""),
|
|
73
|
-
timestamp: new Date(String(m.timestamp)),
|
|
74
|
-
};
|
|
75
|
-
});
|
|
76
|
-
} catch (error) {
|
|
77
|
-
logger.warn("Failed to load agent memory, falling back to empty", {
|
|
78
|
-
tenantId,
|
|
79
|
-
conversationId,
|
|
80
|
-
error,
|
|
81
|
-
});
|
|
82
|
-
return [];
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Persist messages to Redis with TTL.
|
|
88
|
-
* Silently skips when Redis is unavailable.
|
|
89
|
-
*/
|
|
90
|
-
export async function saveMemory(
|
|
91
|
-
tenantId: string,
|
|
92
|
-
conversationId: string,
|
|
93
|
-
messages: readonly AgentMessage[],
|
|
94
|
-
): Promise<void> {
|
|
95
|
-
const redis = await tryGetRedis();
|
|
96
|
-
if (!redis) return;
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const key = memoryKey(tenantId, conversationId);
|
|
100
|
-
await redis.set(key, JSON.stringify(messages), { ex: getTtl() });
|
|
101
|
-
} catch (error) {
|
|
102
|
-
logger.warn("Failed to save agent memory", {
|
|
103
|
-
tenantId,
|
|
104
|
-
conversationId,
|
|
105
|
-
error,
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Clear conversation memory for a tenant/conversation pair.
|
|
112
|
-
*/
|
|
113
|
-
export async function clearMemory(tenantId: string, conversationId: string): Promise<void> {
|
|
114
|
-
const redis = await tryGetRedis();
|
|
115
|
-
if (!redis) return;
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
await redis.del(memoryKey(tenantId, conversationId));
|
|
119
|
-
} catch (error) {
|
|
120
|
-
logger.warn("Failed to clear agent memory", {
|
|
121
|
-
tenantId,
|
|
122
|
-
conversationId,
|
|
123
|
-
error,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
}
|
package/src/observability.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LLM observability via Langfuse.
|
|
3
|
-
*
|
|
4
|
-
* - No-op when env vars are missing — the package works with zero config.
|
|
5
|
-
* - Exposes `experimental_telemetry` settings ready to plug into Vercel AI SDK.
|
|
6
|
-
* - Use `LangfuseExporter` from `langfuse-vercel` in your OTEL NodeSDK setup
|
|
7
|
-
* for full trace export (see README).
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { logger } from "@nebutra/logger";
|
|
11
|
-
import { getAgentsEnv, isLangfuseConfigured } from "./env";
|
|
12
|
-
|
|
13
|
-
// `Langfuse` client is dynamically imported so the package starts up
|
|
14
|
-
// without telemetry deps when they are not used.
|
|
15
|
-
type LangfuseClient = {
|
|
16
|
-
trace: (input: unknown) => unknown;
|
|
17
|
-
flushAsync: () => Promise<void>;
|
|
18
|
-
shutdownAsync: () => Promise<void>;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
let _client: LangfuseClient | null | undefined;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Returns a configured `Langfuse` client, or `null` when env is missing.
|
|
25
|
-
*
|
|
26
|
-
* Telemetry is OPTIONAL. If LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY are
|
|
27
|
-
* not set, this returns null (no error). Callers must handle the null case.
|
|
28
|
-
*/
|
|
29
|
-
export async function initLangfuse(): Promise<LangfuseClient | null> {
|
|
30
|
-
if (_client !== undefined) return _client;
|
|
31
|
-
|
|
32
|
-
if (!isLangfuseConfigured()) {
|
|
33
|
-
_client = null;
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
const env = getAgentsEnv();
|
|
39
|
-
const { Langfuse } = await import("langfuse");
|
|
40
|
-
_client = new Langfuse({
|
|
41
|
-
publicKey: env.LANGFUSE_PUBLIC_KEY!,
|
|
42
|
-
secretKey: env.LANGFUSE_SECRET_KEY!,
|
|
43
|
-
baseUrl: env.LANGFUSE_HOST,
|
|
44
|
-
}) as unknown as LangfuseClient;
|
|
45
|
-
logger.info("Langfuse telemetry enabled", { host: env.LANGFUSE_HOST });
|
|
46
|
-
return _client;
|
|
47
|
-
} catch (error) {
|
|
48
|
-
logger.warn("Failed to initialise Langfuse — telemetry disabled", { error });
|
|
49
|
-
_client = null;
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Test helper — clears cached client so subsequent init() re-reads env. */
|
|
55
|
-
export function _resetLangfuseCache(): void {
|
|
56
|
-
_client = undefined;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface TelemetryMetadata {
|
|
60
|
-
tenantId?: string | undefined;
|
|
61
|
-
userId?: string | undefined;
|
|
62
|
-
sessionId?: string | undefined;
|
|
63
|
-
agentId?: string | undefined;
|
|
64
|
-
[key: string]: unknown;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Build the `experimental_telemetry` option for AI SDK calls.
|
|
69
|
-
* Returns `{ isEnabled: false }` (a safe no-op) when Langfuse is not configured,
|
|
70
|
-
* which avoids any OTEL span creation cost.
|
|
71
|
-
*/
|
|
72
|
-
export function buildTelemetryConfig(args: { functionId: string; metadata?: TelemetryMetadata }): {
|
|
73
|
-
isEnabled: boolean;
|
|
74
|
-
functionId?: string;
|
|
75
|
-
metadata?: Record<string, unknown>;
|
|
76
|
-
} {
|
|
77
|
-
if (!isLangfuseConfigured()) {
|
|
78
|
-
return { isEnabled: false };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
isEnabled: true,
|
|
83
|
-
functionId: args.functionId,
|
|
84
|
-
metadata: {
|
|
85
|
-
...(args.metadata ?? {}),
|
|
86
|
-
// Langfuse picks up these conventional keys from metadata
|
|
87
|
-
...(args.metadata?.tenantId ? { langfuseUserId: args.metadata.tenantId } : {}),
|
|
88
|
-
...(args.metadata?.sessionId ? { langfuseSessionId: args.metadata.sessionId } : {}),
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Flush pending telemetry before process exit. Safe to call when disabled. */
|
|
94
|
-
export async function flushTelemetry(): Promise<void> {
|
|
95
|
-
const client = await initLangfuse();
|
|
96
|
-
if (!client) return;
|
|
97
|
-
try {
|
|
98
|
-
await client.flushAsync();
|
|
99
|
-
} catch (error) {
|
|
100
|
-
logger.warn("Langfuse flush failed", { error });
|
|
101
|
-
}
|
|
102
|
-
}
|
package/src/orchestrator.ts
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentOrchestrator — multi-agent coordination engine.
|
|
3
|
-
*
|
|
4
|
-
* Supports three execution modes:
|
|
5
|
-
* - chat(): route a single message to the best agent
|
|
6
|
-
* - pipeline(): chain agents sequentially (output → next input)
|
|
7
|
-
* - broadcast(): fan-out to all agents and collect results
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { logger } from "@nebutra/logger";
|
|
11
|
-
import { BaseAgent } from "./agent";
|
|
12
|
-
import { AgentRouter } from "./router";
|
|
13
|
-
import { checkAgentQuota } from "./tenant";
|
|
14
|
-
import type {
|
|
15
|
-
AgentContext,
|
|
16
|
-
AgentMessage,
|
|
17
|
-
AgentResponse,
|
|
18
|
-
OrchestratorConfig,
|
|
19
|
-
PipelineStep,
|
|
20
|
-
} from "./types";
|
|
21
|
-
|
|
22
|
-
export class AgentOrchestrator {
|
|
23
|
-
private readonly agents: Map<string, BaseAgent>;
|
|
24
|
-
private readonly router: AgentRouter;
|
|
25
|
-
private readonly defaultAgentId: string | undefined;
|
|
26
|
-
|
|
27
|
-
constructor(config: OrchestratorConfig) {
|
|
28
|
-
this.agents = new Map();
|
|
29
|
-
this.defaultAgentId = config.defaultAgentId;
|
|
30
|
-
|
|
31
|
-
// Register agents — callers provide AgentConfig[], we wrap in BaseAgent
|
|
32
|
-
// In practice, callers will register concrete subclasses (VercelAIAgent, etc.)
|
|
33
|
-
for (const agentConfig of config.agents) {
|
|
34
|
-
this.agents.set(agentConfig.id, new BaseAgent(agentConfig));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Set up router
|
|
38
|
-
this.router = new AgentRouter(config.router ?? { strategy: "keyword" });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Register a pre-built agent instance (e.g. VercelAIAgent).
|
|
43
|
-
* Overwrites any agent with the same ID.
|
|
44
|
-
*/
|
|
45
|
-
registerAgent(agent: BaseAgent): void {
|
|
46
|
-
this.agents.set(agent.config.id, agent);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Get a registered agent by ID.
|
|
51
|
-
*/
|
|
52
|
-
getAgent(agentId: string): BaseAgent | undefined {
|
|
53
|
-
return this.agents.get(agentId);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Route a message to the best agent and execute.
|
|
58
|
-
*/
|
|
59
|
-
async chat(message: string, context: AgentContext): Promise<AgentResponse> {
|
|
60
|
-
await this.assertQuota(context.tenantId);
|
|
61
|
-
|
|
62
|
-
const agentConfigs = [...this.agents.values()].map((a) => a.config);
|
|
63
|
-
const agentId = await this.router.route(message, agentConfigs, context, this.defaultAgentId);
|
|
64
|
-
|
|
65
|
-
const agent = this.agents.get(agentId);
|
|
66
|
-
if (!agent) {
|
|
67
|
-
throw new Error(`Agent "${agentId}" not found in orchestrator`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const messages: AgentMessage[] = [{ role: "user", content: message, timestamp: new Date() }];
|
|
71
|
-
|
|
72
|
-
return agent.run(messages, context);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Execute a multi-agent pipeline where each step's output feeds the next.
|
|
77
|
-
* @experimental — API may change. Use `chat()` for production workloads.
|
|
78
|
-
*/
|
|
79
|
-
async pipeline(
|
|
80
|
-
steps: readonly PipelineStep[],
|
|
81
|
-
input: string,
|
|
82
|
-
context: AgentContext,
|
|
83
|
-
): Promise<AgentResponse> {
|
|
84
|
-
await this.assertQuota(context.tenantId);
|
|
85
|
-
|
|
86
|
-
let currentInput = input;
|
|
87
|
-
let lastResponse: AgentResponse | undefined;
|
|
88
|
-
|
|
89
|
-
for (const step of steps) {
|
|
90
|
-
const agent = this.agents.get(step.agentId);
|
|
91
|
-
if (!agent) {
|
|
92
|
-
throw new Error(`Pipeline step references unknown agent "${step.agentId}"`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const transformedInput = step.transformInput
|
|
96
|
-
? step.transformInput(currentInput)
|
|
97
|
-
: currentInput;
|
|
98
|
-
|
|
99
|
-
const messages: AgentMessage[] = [
|
|
100
|
-
{ role: "user", content: transformedInput, timestamp: new Date() },
|
|
101
|
-
];
|
|
102
|
-
|
|
103
|
-
lastResponse = await agent.run(messages, context);
|
|
104
|
-
|
|
105
|
-
// Extract the last assistant message as input for the next step
|
|
106
|
-
const assistantMessages = lastResponse.messages.filter((m) => m.role === "assistant");
|
|
107
|
-
const lastAssistant = assistantMessages[assistantMessages.length - 1];
|
|
108
|
-
currentInput = lastAssistant?.content ?? "";
|
|
109
|
-
|
|
110
|
-
logger.info("Pipeline step completed", {
|
|
111
|
-
agentId: step.agentId,
|
|
112
|
-
tenantId: context.tenantId,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (!lastResponse) {
|
|
117
|
-
throw new Error("Pipeline produced no response (empty steps?)");
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return lastResponse;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Broadcast a message to ALL registered agents in parallel.
|
|
125
|
-
* Returns an array of responses (one per agent).
|
|
126
|
-
* @experimental — API may change. Use `chat()` for production workloads.
|
|
127
|
-
*/
|
|
128
|
-
async broadcast(message: string, context: AgentContext): Promise<readonly AgentResponse[]> {
|
|
129
|
-
await this.assertQuota(context.tenantId);
|
|
130
|
-
|
|
131
|
-
const messages: AgentMessage[] = [{ role: "user", content: message, timestamp: new Date() }];
|
|
132
|
-
|
|
133
|
-
const promises = [...this.agents.values()].map((agent) => agent.run(messages, context));
|
|
134
|
-
|
|
135
|
-
return Promise.all(promises);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Check tenant quota before execution.
|
|
140
|
-
*/
|
|
141
|
-
private async assertQuota(tenantId: string): Promise<void> {
|
|
142
|
-
const { allowed } = await checkAgentQuota(tenantId);
|
|
143
|
-
if (!allowed) {
|
|
144
|
-
throw new Error(`Tenant "${tenantId}" has exceeded agent execution quota`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LangChain.js agent adapter — stub.
|
|
3
|
-
*
|
|
4
|
-
* This module intentionally throws at construction time to guide
|
|
5
|
-
* developers through the required dependency installation.
|
|
6
|
-
*
|
|
7
|
-
* Once the packages are installed, implement the `execute()` method
|
|
8
|
-
* using LangChain's AgentExecutor or the newer LangGraph approach.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { BaseAgent } from "../agent";
|
|
12
|
-
import type { AgentConfig } from "../types";
|
|
13
|
-
|
|
14
|
-
export class LangChainAgent extends BaseAgent {
|
|
15
|
-
constructor(config: AgentConfig) {
|
|
16
|
-
super(config);
|
|
17
|
-
throw new Error(
|
|
18
|
-
[
|
|
19
|
-
"LangChain agent requires additional packages. Install:",
|
|
20
|
-
"",
|
|
21
|
-
" pnpm add langchain @langchain/core @langchain/openai",
|
|
22
|
-
"",
|
|
23
|
-
"Then implement the execute() method in this file using",
|
|
24
|
-
"LangChain's AgentExecutor or LangGraph.",
|
|
25
|
-
].join("\n"),
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vercel AI SDK agent adapter.
|
|
3
|
-
*
|
|
4
|
-
* Uses `streamText` from the `ai` package with tool-loop support.
|
|
5
|
-
* The `ai` peer dependency is dynamically imported so the package
|
|
6
|
-
* doesn't fail at require-time when the SDK is absent.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { BaseAgent } from "../agent";
|
|
10
|
-
import { runWithFallback, withAnthropicCacheControl } from "../fallback";
|
|
11
|
-
import { buildTelemetryConfig } from "../observability";
|
|
12
|
-
import type { AgentContext, AgentMessage, AgentResponse } from "../types";
|
|
13
|
-
|
|
14
|
-
export class VercelAIAgent extends BaseAgent {
|
|
15
|
-
protected override async execute(
|
|
16
|
-
messages: readonly AgentMessage[],
|
|
17
|
-
context: AgentContext,
|
|
18
|
-
): Promise<AgentResponse> {
|
|
19
|
-
// Dynamic import — avoids hard dependency on `ai`
|
|
20
|
-
const { streamText, stepCountIs, dynamicTool } = await import("ai");
|
|
21
|
-
|
|
22
|
-
type StreamTextParams = Parameters<typeof streamText>[0];
|
|
23
|
-
|
|
24
|
-
// Build AI SDK tool definitions from our AgentTool interface.
|
|
25
|
-
// We use dynamicTool() because our AgentTool uses a loose JSON Schema
|
|
26
|
-
// record type rather than a typed Zod schema.
|
|
27
|
-
const toolSet: StreamTextParams["tools"] = this.config.tools
|
|
28
|
-
? Object.fromEntries(
|
|
29
|
-
this.config.tools.map((t) => [
|
|
30
|
-
t.name,
|
|
31
|
-
dynamicTool({
|
|
32
|
-
description: t.description,
|
|
33
|
-
inputSchema: t.inputSchema as Parameters<typeof dynamicTool>[0]["inputSchema"],
|
|
34
|
-
execute: async (args) => t.execute(args, context),
|
|
35
|
-
}),
|
|
36
|
-
]),
|
|
37
|
-
)
|
|
38
|
-
: undefined;
|
|
39
|
-
|
|
40
|
-
// Build streamText options, conditionally including tools to satisfy
|
|
41
|
-
// exactOptionalPropertyTypes (tools must not be `undefined`).
|
|
42
|
-
//
|
|
43
|
-
// Cost optimization: stable content (system prompt + tool defs) is placed
|
|
44
|
-
// FIRST and dynamic content (user messages) LAST. This ordering is required
|
|
45
|
-
// for both Anthropic explicit prompt caching (90% discount via `cacheControl`)
|
|
46
|
-
// and OpenAI automatic caching (≥1024 token stable prefix). Reordering or
|
|
47
|
-
// mutating the system prompt invalidates the cache on every call.
|
|
48
|
-
const telemetry = buildTelemetryConfig({
|
|
49
|
-
functionId: `agent.${this.config.id}`,
|
|
50
|
-
metadata: {
|
|
51
|
-
tenantId: context.tenantId,
|
|
52
|
-
userId: context.userId,
|
|
53
|
-
sessionId: context.conversationId,
|
|
54
|
-
agentId: this.config.id,
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const baseOptionsWithoutModel = {
|
|
59
|
-
system: this.config.instructions,
|
|
60
|
-
messages: messages.map((m) => ({
|
|
61
|
-
role: m.role as "user" | "assistant" | "system",
|
|
62
|
-
content: m.content,
|
|
63
|
-
})),
|
|
64
|
-
stopWhen: stepCountIs(this.config.maxSteps ?? 20),
|
|
65
|
-
// Anthropic prompt cache control on the system message — 90% cost
|
|
66
|
-
// reduction on cached prefix tokens. No-op for non-Anthropic providers.
|
|
67
|
-
providerOptions: withAnthropicCacheControl(),
|
|
68
|
-
experimental_telemetry: telemetry,
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// Run through the multi-provider fallback chain. The chain is filtered
|
|
72
|
-
// to providers with API keys present (so single-provider deploys are
|
|
73
|
-
// backward-compatible — only the configured provider is tried).
|
|
74
|
-
const { result } = await runWithFallback(
|
|
75
|
-
async (model) => {
|
|
76
|
-
const streamOptions = {
|
|
77
|
-
...baseOptionsWithoutModel,
|
|
78
|
-
model,
|
|
79
|
-
...(toolSet !== undefined ? { tools: toolSet } : {}),
|
|
80
|
-
} as StreamTextParams;
|
|
81
|
-
const r = streamText(streamOptions);
|
|
82
|
-
// Materialize the result so retryable errors surface inside the
|
|
83
|
-
// try/catch in runWithFallback() rather than escaping as unhandled
|
|
84
|
-
// rejections from the lazy stream.
|
|
85
|
-
const text = await r.text;
|
|
86
|
-
const usage = await r.usage;
|
|
87
|
-
return { text, usage };
|
|
88
|
-
},
|
|
89
|
-
{ model: this.config.model },
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
const { text, usage } = result;
|
|
93
|
-
|
|
94
|
-
// AI SDK v6 uses inputTokens/outputTokens on LanguageModelUsage
|
|
95
|
-
const inputTokens = usage?.inputTokens ?? 0;
|
|
96
|
-
const outputTokens = usage?.outputTokens ?? 0;
|
|
97
|
-
|
|
98
|
-
const responseMessages: AgentMessage[] = [
|
|
99
|
-
...messages,
|
|
100
|
-
{ role: "assistant" as const, content: text, timestamp: new Date() },
|
|
101
|
-
];
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
messages: responseMessages,
|
|
105
|
-
usage: {
|
|
106
|
-
promptTokens: inputTokens,
|
|
107
|
-
completionTokens: outputTokens,
|
|
108
|
-
totalTokens: inputTokens + outputTokens,
|
|
109
|
-
},
|
|
110
|
-
finishReason: "stop",
|
|
111
|
-
agentId: this.config.id,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
}
|