@nebutra/agents 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +40 -0
- package/.turbo/turbo-test.log +18 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +26 -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 +4 -3
- package/src/__tests__/generation.test.ts +111 -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
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
> @nebutra/agents@1.1.0 build /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/agents
|
|
3
|
+
> tsup
|
|
4
|
+
|
|
5
|
+
[34mCLI[39m Building entry: src/index.ts, src/tools.ts, src/providers/langchain.ts, src/providers/vercel-ai.ts, src/sdk/config.ts, src/sdk/index.ts, src/sdk/models.ts, src/sdk/provider.ts
|
|
6
|
+
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
|
+
[34mCLI[39m tsup v8.5.1
|
|
8
|
+
[34mCLI[39m Using tsup config: /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/agents/tsup.config.ts
|
|
9
|
+
[34mCLI[39m Target: node20
|
|
10
|
+
[34mCLI[39m Cleaning output folder
|
|
11
|
+
[34mESM[39m Build start
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m16.08 KB[39m
|
|
13
|
+
[32mESM[39m [1mdist/providers/langchain.js [22m[32m536.00 B[39m
|
|
14
|
+
[32mESM[39m [1mdist/sdk/config.js [22m[32m166.00 B[39m
|
|
15
|
+
[32mESM[39m [1mdist/tools.js [22m[32m203.00 B[39m
|
|
16
|
+
[32mESM[39m [1mdist/chunk-UVL2UVVM.js [22m[32m1.26 KB[39m
|
|
17
|
+
[32mESM[39m [1mdist/providers/vercel-ai.js [22m[32m2.43 KB[39m
|
|
18
|
+
[32mESM[39m [1mdist/chunk-B7XWL35G.js [22m[32m1.51 KB[39m
|
|
19
|
+
[32mESM[39m [1mdist/chunk-RDOFKRI6.js [22m[32m4.71 KB[39m
|
|
20
|
+
[32mESM[39m [1mdist/sdk/index.js [22m[32m534.00 B[39m
|
|
21
|
+
[32mESM[39m [1mdist/chunk-5LX742GP.js [22m[32m7.78 KB[39m
|
|
22
|
+
[32mESM[39m [1mdist/chunk-NPQECBXL.js [22m[32m2.37 KB[39m
|
|
23
|
+
[32mESM[39m [1mdist/sdk/models.js [22m[32m102.00 B[39m
|
|
24
|
+
[32mESM[39m [1mdist/chunk-RLWM437Q.js [22m[32m1.63 KB[39m
|
|
25
|
+
[32mESM[39m [1mdist/chunk-NVPE5EDI.js [22m[32m1.49 KB[39m
|
|
26
|
+
[32mESM[39m [1mdist/chunk-5JZJ5KMC.js [22m[32m1.22 KB[39m
|
|
27
|
+
[32mESM[39m [1mdist/sdk/provider.js [22m[32m190.00 B[39m
|
|
28
|
+
[32mESM[39m ⚡️ Build success in 33ms
|
|
29
|
+
[34mDTS[39m Build start
|
|
30
|
+
[32mDTS[39m ⚡️ Build success in 5153ms
|
|
31
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m19.71 KB[39m
|
|
32
|
+
[32mDTS[39m [1mdist/tools.d.ts [22m[32m724.00 B[39m
|
|
33
|
+
[32mDTS[39m [1mdist/providers/langchain.d.ts [22m[32m546.00 B[39m
|
|
34
|
+
[32mDTS[39m [1mdist/providers/vercel-ai.d.ts [22m[32m569.00 B[39m
|
|
35
|
+
[32mDTS[39m [1mdist/sdk/index.d.ts [22m[32m3.88 KB[39m
|
|
36
|
+
[32mDTS[39m [1mdist/sdk/models.d.ts [22m[32m1.73 KB[39m
|
|
37
|
+
[32mDTS[39m [1mdist/sdk/provider.d.ts [22m[32m882.00 B[39m
|
|
38
|
+
[32mDTS[39m [1mdist/sdk/config.d.ts [22m[32m1.80 KB[39m
|
|
39
|
+
[32mDTS[39m [1mdist/agent-CahnMASx.d.ts [22m[32m1.10 KB[39m
|
|
40
|
+
[32mDTS[39m [1mdist/types-NtgB3pch.d.ts [22m[32m3.02 KB[39m
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
> @nebutra/agents@1.1.0 test /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/agents
|
|
3
|
+
> vitest run
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
[1m[30m[46m RUN [49m[39m[22m [36mv4.1.4 [39m[90m/home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/agents[39m
|
|
7
|
+
|
|
8
|
+
[32m✓[39m src/__tests__/fallback-wiring.test.ts [2m([22m[2m9 tests[22m[2m)[22m[32m 253[2mms[22m[39m
|
|
9
|
+
[32m✓[39m src/__tests__/cost-observability.test.ts [2m([22m[2m18 tests[22m[2m)[22m[33m 655[2mms[22m[39m
|
|
10
|
+
[33m[2m✓[22m[39m falls through retryable errors and returns the next provider's result [33m 537[2mms[22m[39m
|
|
11
|
+
[32m✓[39m src/__tests__/public-api.test.ts [2m([22m[2m16 tests[22m[2m)[22m[32m 59[2mms[22m[39m
|
|
12
|
+
[32m✓[39m src/__tests__/generation.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m 43[2mms[22m[39m
|
|
13
|
+
|
|
14
|
+
[2m Test Files [22m [1m[32m4 passed[39m[22m[90m (4)[39m
|
|
15
|
+
[2m Tests [22m [1m[32m50 passed[39m[22m[90m (50)[39m
|
|
16
|
+
[2m Start at [22m 16:51:18
|
|
17
|
+
[2m Duration [22m 4.50s[2m (transform 2.59s, setup 0ms, import 6.35s, tests 1.01s, environment 1ms)[22m
|
|
18
|
+
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# @nebutra/agents
|
|
2
|
+
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`092a1ce`](https://github.com/Nebutra/Nebutra-Sailor/commit/092a1ce810965e2d81767642e4bad05b80df81f4) Thanks [@TsekaLuk](https://github.com/TsekaLuk)! - Add the Atelier agentic creative-canvas capability.
|
|
8
|
+
|
|
9
|
+
- `@nebutra/agents`: new image/video **generation modality**
|
|
10
|
+
(`@nebutra/agents/generation`) on the same env-key-gated provider layer as
|
|
11
|
+
the LLM fallback chain, with a deterministic always-available mock provider.
|
|
12
|
+
- `@nebutra/atelier-canvas`: new package — server-authoritative non-overlap
|
|
13
|
+
placement, persist-then-broadcast consistency, per-`(tenant,canvas)`
|
|
14
|
+
serialization, tenant-scoped store (in-memory + Prisma adapter). The
|
|
15
|
+
`./agent` subpath adds the creative-direction prompt strategy + generation
|
|
16
|
+
tool + `AgentConfig` factory (optional `@nebutra/agents` peer).
|
|
17
|
+
- `@nebutra/feature-flags`: new `FLAGS.ATELIER_CANVAS` (off by default).
|
|
18
|
+
|
|
19
|
+
Additive `AtelierCanvas` Prisma model. A flag-gated `/atelier` demo route in
|
|
20
|
+
`apps/web` exercises the loop end-to-end with the mock provider (no AI key
|
|
21
|
+
required).
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- Updated dependencies [[`5d3d7e6`](https://github.com/Nebutra/Nebutra-Sailor/commit/5d3d7e6c59cae5aa242bb988b75a9888cfd0db39)]:
|
|
26
|
+
- @nebutra/billing@0.1.1
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { c as AgentConfig, A as AgentMessage, a as AgentContext, b as AgentResponse } from './types-NtgB3pch.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BaseAgent — abstract agent with memory, usage tracking, and tenant scoping.
|
|
5
|
+
*
|
|
6
|
+
* Concrete agents (VercelAIAgent, LangChainAgent, etc.) extend this class
|
|
7
|
+
* and implement the `execute()` method for their specific SDK.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
declare class BaseAgent {
|
|
11
|
+
readonly config: AgentConfig;
|
|
12
|
+
constructor(config: AgentConfig);
|
|
13
|
+
/**
|
|
14
|
+
* Run the agent with full lifecycle:
|
|
15
|
+
* 1. Load long-term memory (if configured)
|
|
16
|
+
* 2. Trim to short-term window
|
|
17
|
+
* 3. Delegate to provider-specific `execute()`
|
|
18
|
+
* 4. Track usage for billing
|
|
19
|
+
* 5. Persist new messages to memory
|
|
20
|
+
*/
|
|
21
|
+
run(messages: readonly AgentMessage[], context: AgentContext): Promise<AgentResponse>;
|
|
22
|
+
/**
|
|
23
|
+
* Provider-specific execution. Subclasses MUST override this.
|
|
24
|
+
*/
|
|
25
|
+
protected execute(_messages: readonly AgentMessage[], _context: AgentContext): Promise<AgentResponse>;
|
|
26
|
+
/**
|
|
27
|
+
* Emit a usage event for downstream billing / metering.
|
|
28
|
+
*/
|
|
29
|
+
private emitUsage;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { BaseAgent as B };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/sdk/models.ts
|
|
2
|
+
var models = {
|
|
3
|
+
/** High-quality reasoning — default for complex tasks */
|
|
4
|
+
flagship: "anthropic/claude-sonnet-4",
|
|
5
|
+
/** Deep reasoning for architecture and research */
|
|
6
|
+
reasoning: "anthropic/claude-opus-4",
|
|
7
|
+
/** Fast + cheap — chat, summaries, classification */
|
|
8
|
+
fast: "anthropic/claude-haiku-4",
|
|
9
|
+
/** OpenAI flagship */
|
|
10
|
+
"openai-flagship": "openai/gpt-5.4",
|
|
11
|
+
/** Google flagship */
|
|
12
|
+
"google-flagship": "google/gemini-2.5-pro",
|
|
13
|
+
/** Google fast */
|
|
14
|
+
"google-fast": "google/gemini-2.5-flash",
|
|
15
|
+
/** Embedding model */
|
|
16
|
+
embedding: "openai/text-embedding-3-small",
|
|
17
|
+
/** Embedding model (high-dimensional) */
|
|
18
|
+
"embedding-large": "openai/text-embedding-3-large",
|
|
19
|
+
// --- SiliconFlow presets (use with provider: "siliconflow") ---
|
|
20
|
+
/** SiliconFlow — Qwen 2.5 72B (flagship open-source) */
|
|
21
|
+
"sf-qwen": "Qwen/Qwen2.5-72B-Instruct",
|
|
22
|
+
/** SiliconFlow — DeepSeek R1 reasoning (via SiliconFlow Pro) */
|
|
23
|
+
"sf-deepseek-r1": "Pro/deepseek-ai/DeepSeek-R1",
|
|
24
|
+
/** SiliconFlow — DeepSeek V3 (fast, capable) */
|
|
25
|
+
"sf-deepseek-v3": "deepseek-ai/DeepSeek-V3"
|
|
26
|
+
};
|
|
27
|
+
function resolveModel(modelOrPreset) {
|
|
28
|
+
if (modelOrPreset in models) {
|
|
29
|
+
return models[modelOrPreset];
|
|
30
|
+
}
|
|
31
|
+
return modelOrPreset;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
models,
|
|
36
|
+
resolveModel
|
|
37
|
+
};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveModel
|
|
3
|
+
} from "./chunk-5JZJ5KMC.js";
|
|
4
|
+
|
|
5
|
+
// src/env.ts
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
var FallbackProviderName = z.enum(["openrouter", "anthropic", "openai"]);
|
|
8
|
+
var FallbackChain = z.string().transform(
|
|
9
|
+
(raw) => raw.split(",").map((s) => s.trim()).filter(Boolean)
|
|
10
|
+
).pipe(z.array(FallbackProviderName).min(1));
|
|
11
|
+
var AgentsEnvSchema = z.object({
|
|
12
|
+
// ── Anthropic (direct) ─────────────────────────────────────────────────
|
|
13
|
+
ANTHROPIC_API_KEY: z.string().optional(),
|
|
14
|
+
// ── Langfuse (LLM tracing — optional) ─────────────────────────────────
|
|
15
|
+
LANGFUSE_PUBLIC_KEY: z.string().optional(),
|
|
16
|
+
LANGFUSE_SECRET_KEY: z.string().optional(),
|
|
17
|
+
LANGFUSE_HOST: z.string().url().default("https://cloud.langfuse.com"),
|
|
18
|
+
// ── Multi-provider fallback chain ─────────────────────────────────────
|
|
19
|
+
/**
|
|
20
|
+
* Comma-separated chain of providers tried in order on retryable failures.
|
|
21
|
+
* Default: "openrouter,anthropic,openai" — OpenRouter first (multi-model),
|
|
22
|
+
* then direct Anthropic (prompt caching), then direct OpenAI as last resort.
|
|
23
|
+
*/
|
|
24
|
+
LLM_FALLBACK_CHAIN: FallbackChain.default(() => [
|
|
25
|
+
"openrouter",
|
|
26
|
+
"anthropic",
|
|
27
|
+
"openai"
|
|
28
|
+
]),
|
|
29
|
+
/**
|
|
30
|
+
* Comma-separated chain of providers tried for EMBEDDINGS, in order.
|
|
31
|
+
* Default: "openrouter,openai" — Anthropic does not currently expose
|
|
32
|
+
* embedding models, so it is excluded by default. If unset, falls back
|
|
33
|
+
* to LLM_FALLBACK_CHAIN with embedding-incompatible providers filtered out.
|
|
34
|
+
*/
|
|
35
|
+
LLM_EMBEDDING_FALLBACK_CHAIN: FallbackChain.default(() => [
|
|
36
|
+
"openrouter",
|
|
37
|
+
"openai"
|
|
38
|
+
])
|
|
39
|
+
});
|
|
40
|
+
var _cached;
|
|
41
|
+
function getAgentsEnv() {
|
|
42
|
+
if (_cached) return _cached;
|
|
43
|
+
_cached = AgentsEnvSchema.parse(globalThis.process?.env ?? {});
|
|
44
|
+
return _cached;
|
|
45
|
+
}
|
|
46
|
+
function isLangfuseConfigured() {
|
|
47
|
+
const env = getAgentsEnv();
|
|
48
|
+
return Boolean(env.LANGFUSE_PUBLIC_KEY && env.LANGFUSE_SECRET_KEY);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/fallback.ts
|
|
52
|
+
import { logger } from "@nebutra/logger";
|
|
53
|
+
var ENV_KEY_BY_PROVIDER = {
|
|
54
|
+
openrouter: "OPENROUTER_API_KEY",
|
|
55
|
+
anthropic: "ANTHROPIC".concat("_API_KEY"),
|
|
56
|
+
openai: "OPENAI".concat("_API_KEY")
|
|
57
|
+
};
|
|
58
|
+
function hasProviderKey(provider) {
|
|
59
|
+
const k = ENV_KEY_BY_PROVIDER[provider];
|
|
60
|
+
return Boolean(globalThis.process?.env?.[k]);
|
|
61
|
+
}
|
|
62
|
+
function filterAvailableProviders(chain) {
|
|
63
|
+
const filtered = chain.filter(hasProviderKey);
|
|
64
|
+
return filtered.length > 0 ? filtered : chain;
|
|
65
|
+
}
|
|
66
|
+
async function buildModel(provider, modelOrPreset) {
|
|
67
|
+
const modelId = resolveModel(modelOrPreset);
|
|
68
|
+
const envKey = ENV_KEY_BY_PROVIDER[provider];
|
|
69
|
+
const apiKey = globalThis.process?.env?.[envKey];
|
|
70
|
+
if (!apiKey) throw new Error(`${envKey} missing`);
|
|
71
|
+
switch (provider) {
|
|
72
|
+
case "openrouter": {
|
|
73
|
+
const { createOpenRouter } = await import("@openrouter/ai-sdk-provider");
|
|
74
|
+
return createOpenRouter({ apiKey }).chat(modelId);
|
|
75
|
+
}
|
|
76
|
+
case "anthropic": {
|
|
77
|
+
const { createAnthropic } = await import("@ai-sdk/anthropic");
|
|
78
|
+
const anthropicModelId = modelId.startsWith("anthropic/") ? modelId.slice("anthropic/".length) : modelId;
|
|
79
|
+
return createAnthropic({ apiKey })(anthropicModelId);
|
|
80
|
+
}
|
|
81
|
+
case "openai": {
|
|
82
|
+
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
83
|
+
const openaiModelId = modelId.startsWith("openai/") ? modelId.slice("openai/".length) : modelId;
|
|
84
|
+
return createOpenAI({ apiKey })(openaiModelId);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
89
|
+
function isRetryableError(error) {
|
|
90
|
+
if (!error || typeof error !== "object") return false;
|
|
91
|
+
const e = error;
|
|
92
|
+
const status = typeof e.statusCode === "number" ? e.statusCode : typeof e.status === "number" ? e.status : void 0;
|
|
93
|
+
if (status !== void 0 && RETRYABLE_STATUS.has(status)) return true;
|
|
94
|
+
const code = typeof e.code === "string" ? e.code : "";
|
|
95
|
+
if (code === "ECONNRESET" || code === "ETIMEDOUT" || code === "ENOTFOUND" || code === "EAI_AGAIN") {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
if (e.isRetryable === true) return true;
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
async function runWithFallback(invoke, options = {}) {
|
|
102
|
+
const env = getAgentsEnv();
|
|
103
|
+
const rawChain = options.chain ?? env.LLM_FALLBACK_CHAIN;
|
|
104
|
+
const chain = options.filterAvailable === false ? rawChain : filterAvailableProviders(rawChain);
|
|
105
|
+
const model = options.model ?? "flagship";
|
|
106
|
+
let lastError;
|
|
107
|
+
let attempts = 0;
|
|
108
|
+
for (const provider of chain) {
|
|
109
|
+
attempts += 1;
|
|
110
|
+
try {
|
|
111
|
+
const lm = await buildModel(provider, model);
|
|
112
|
+
const result = await invoke(lm);
|
|
113
|
+
if (attempts > 1) {
|
|
114
|
+
logger.info("LLM fallback succeeded", { provider, attempts });
|
|
115
|
+
}
|
|
116
|
+
return { result, provider, attempts };
|
|
117
|
+
} catch (error) {
|
|
118
|
+
lastError = error;
|
|
119
|
+
if (!isRetryableError(error)) {
|
|
120
|
+
logger.error("LLM call failed (non-retryable)", { provider, error });
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
logger.warn("LLM provider failed \u2014 trying next in chain", {
|
|
124
|
+
provider,
|
|
125
|
+
nextIndex: attempts,
|
|
126
|
+
error
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
throw new Error(
|
|
131
|
+
`All LLM providers in fallback chain [${chain.join(", ")}] failed. Last error: ${String(lastError)}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
function withAnthropicCacheControl() {
|
|
135
|
+
return {
|
|
136
|
+
anthropic: { cacheControl: { type: "ephemeral" } }
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function buildSystemWithCache(systemPrompt) {
|
|
140
|
+
return {
|
|
141
|
+
role: "system",
|
|
142
|
+
content: systemPrompt,
|
|
143
|
+
providerOptions: withAnthropicCacheControl()
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
var EMBEDDING_CAPABLE = /* @__PURE__ */ new Set([
|
|
147
|
+
"openrouter",
|
|
148
|
+
"openai"
|
|
149
|
+
]);
|
|
150
|
+
async function buildEmbeddingModel(provider, modelOrPreset) {
|
|
151
|
+
const modelId = resolveModel(modelOrPreset);
|
|
152
|
+
const envKey = ENV_KEY_BY_PROVIDER[provider];
|
|
153
|
+
const apiKey = globalThis.process?.env?.[envKey];
|
|
154
|
+
if (!apiKey) throw new Error(`${envKey} missing`);
|
|
155
|
+
switch (provider) {
|
|
156
|
+
case "openrouter": {
|
|
157
|
+
const { createOpenRouter } = await import("@openrouter/ai-sdk-provider");
|
|
158
|
+
return createOpenRouter({ apiKey }).textEmbeddingModel(modelId);
|
|
159
|
+
}
|
|
160
|
+
case "openai": {
|
|
161
|
+
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
162
|
+
const openaiModelId = modelId.startsWith("openai/") ? modelId.slice("openai/".length) : modelId;
|
|
163
|
+
return createOpenAI({ apiKey }).textEmbeddingModel(openaiModelId);
|
|
164
|
+
}
|
|
165
|
+
case "anthropic": {
|
|
166
|
+
throw new Error("Anthropic does not expose embedding models");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function runEmbedWithFallback(invoke, options = {}) {
|
|
171
|
+
const env = getAgentsEnv();
|
|
172
|
+
const rawChain = options.chain ?? env.LLM_EMBEDDING_FALLBACK_CHAIN;
|
|
173
|
+
const capable = rawChain.filter((p) => EMBEDDING_CAPABLE.has(p));
|
|
174
|
+
const chain = options.filterAvailable === false ? capable : capable.filter(hasProviderKey);
|
|
175
|
+
if (chain.length === 0) {
|
|
176
|
+
logger.warn("Embedding fallback chain is empty after filtering", {
|
|
177
|
+
raw: rawChain,
|
|
178
|
+
capable
|
|
179
|
+
});
|
|
180
|
+
throw new Error(
|
|
181
|
+
"No embedding-capable providers available \u2014 set OPENROUTER_API_KEY or OPENAI_API_KEY (Anthropic does not expose embedding models)."
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const model = options.model ?? "embedding";
|
|
185
|
+
let lastError;
|
|
186
|
+
let attempts = 0;
|
|
187
|
+
for (const provider of chain) {
|
|
188
|
+
attempts += 1;
|
|
189
|
+
try {
|
|
190
|
+
const em = await buildEmbeddingModel(provider, model);
|
|
191
|
+
const result = await invoke(em);
|
|
192
|
+
if (attempts > 1) {
|
|
193
|
+
logger.info("Embedding fallback succeeded", { provider, attempts });
|
|
194
|
+
}
|
|
195
|
+
return { result, provider, attempts };
|
|
196
|
+
} catch (error) {
|
|
197
|
+
lastError = error;
|
|
198
|
+
if (!isRetryableError(error)) {
|
|
199
|
+
logger.error("Embedding call failed (non-retryable)", { provider, error });
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
logger.warn("Embedding provider failed \u2014 trying next in chain", {
|
|
203
|
+
provider,
|
|
204
|
+
nextIndex: attempts,
|
|
205
|
+
error
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
throw new Error(
|
|
210
|
+
`All embedding providers in fallback chain [${chain.join(", ")}] failed. Last error: ${String(lastError)}`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export {
|
|
215
|
+
AgentsEnvSchema,
|
|
216
|
+
getAgentsEnv,
|
|
217
|
+
isLangfuseConfigured,
|
|
218
|
+
filterAvailableProviders,
|
|
219
|
+
isRetryableError,
|
|
220
|
+
runWithFallback,
|
|
221
|
+
withAnthropicCacheControl,
|
|
222
|
+
buildSystemWithCache,
|
|
223
|
+
runEmbedWithFallback
|
|
224
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getAgentsEnv,
|
|
3
|
+
isLangfuseConfigured
|
|
4
|
+
} from "./chunk-5LX742GP.js";
|
|
5
|
+
|
|
6
|
+
// src/observability.ts
|
|
7
|
+
import { logger } from "@nebutra/logger";
|
|
8
|
+
var _client;
|
|
9
|
+
async function initLangfuse() {
|
|
10
|
+
if (_client !== void 0) return _client;
|
|
11
|
+
if (!isLangfuseConfigured()) {
|
|
12
|
+
_client = null;
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const env = getAgentsEnv();
|
|
17
|
+
const { Langfuse } = await import("langfuse");
|
|
18
|
+
_client = new Langfuse({
|
|
19
|
+
publicKey: env.LANGFUSE_PUBLIC_KEY,
|
|
20
|
+
secretKey: env.LANGFUSE_SECRET_KEY,
|
|
21
|
+
baseUrl: env.LANGFUSE_HOST
|
|
22
|
+
});
|
|
23
|
+
logger.info("Langfuse telemetry enabled", { host: env.LANGFUSE_HOST });
|
|
24
|
+
return _client;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
logger.warn("Failed to initialise Langfuse \u2014 telemetry disabled", { error });
|
|
27
|
+
_client = null;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function buildTelemetryConfig(args) {
|
|
32
|
+
if (!isLangfuseConfigured()) {
|
|
33
|
+
return { isEnabled: false };
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
isEnabled: true,
|
|
37
|
+
functionId: args.functionId,
|
|
38
|
+
metadata: {
|
|
39
|
+
...args.metadata ?? {},
|
|
40
|
+
// Langfuse picks up these conventional keys from metadata
|
|
41
|
+
...args.metadata?.tenantId ? { langfuseUserId: args.metadata.tenantId } : {},
|
|
42
|
+
...args.metadata?.sessionId ? { langfuseSessionId: args.metadata.sessionId } : {}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function flushTelemetry() {
|
|
47
|
+
const client = await initLangfuse();
|
|
48
|
+
if (!client) return;
|
|
49
|
+
try {
|
|
50
|
+
await client.flushAsync();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
logger.warn("Langfuse flush failed", { error });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
initLangfuse,
|
|
58
|
+
buildTelemetryConfig,
|
|
59
|
+
flushTelemetry
|
|
60
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runEmbedWithFallback
|
|
3
|
+
} from "./chunk-5LX742GP.js";
|
|
4
|
+
import {
|
|
5
|
+
createModel
|
|
6
|
+
} from "./chunk-RLWM437Q.js";
|
|
7
|
+
import {
|
|
8
|
+
NebutraAIConfigSchema
|
|
9
|
+
} from "./chunk-NVPE5EDI.js";
|
|
10
|
+
|
|
11
|
+
// src/sdk/index.ts
|
|
12
|
+
import {
|
|
13
|
+
embed as _embed,
|
|
14
|
+
embedMany as _embedMany,
|
|
15
|
+
generateText as _generateText,
|
|
16
|
+
streamText as _streamText
|
|
17
|
+
} from "ai";
|
|
18
|
+
var _resolved = NebutraAIConfigSchema.parse({});
|
|
19
|
+
function configure(config = {}) {
|
|
20
|
+
_resolved = NebutraAIConfigSchema.parse(config);
|
|
21
|
+
}
|
|
22
|
+
function getConfig() {
|
|
23
|
+
return _resolved;
|
|
24
|
+
}
|
|
25
|
+
async function generateText(messages, options = {}) {
|
|
26
|
+
const model = createModel(options.model ?? _resolved.defaultModel, _resolved);
|
|
27
|
+
return await _generateText({
|
|
28
|
+
model,
|
|
29
|
+
messages,
|
|
30
|
+
...options.system ? { system: options.system } : {},
|
|
31
|
+
temperature: options.temperature ?? _resolved.temperature,
|
|
32
|
+
...options.maxTokens ? { maxTokens: options.maxTokens } : {},
|
|
33
|
+
...options.providerOptions ? { providerOptions: { openrouter: options.providerOptions } } : {}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async function streamText(messages, options = {}) {
|
|
37
|
+
const model = createModel(options.model ?? _resolved.defaultModel, _resolved);
|
|
38
|
+
const userOnFinish = options.onFinish;
|
|
39
|
+
return _streamText({
|
|
40
|
+
model,
|
|
41
|
+
messages,
|
|
42
|
+
...options.system ? { system: options.system } : {},
|
|
43
|
+
temperature: options.temperature ?? _resolved.temperature,
|
|
44
|
+
...options.maxTokens ? { maxTokens: options.maxTokens } : {},
|
|
45
|
+
...options.providerOptions ? { providerOptions: { openrouter: options.providerOptions } } : {},
|
|
46
|
+
...userOnFinish ? {
|
|
47
|
+
onFinish: ({ text, finishReason, totalUsage }) => {
|
|
48
|
+
const event = {
|
|
49
|
+
text,
|
|
50
|
+
finishReason: String(finishReason),
|
|
51
|
+
usage: {
|
|
52
|
+
inputTokens: totalUsage?.inputTokens,
|
|
53
|
+
outputTokens: totalUsage?.outputTokens,
|
|
54
|
+
totalTokens: totalUsage?.totalTokens
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
return userOnFinish(event);
|
|
58
|
+
}
|
|
59
|
+
} : {}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async function embed(value, options = {}) {
|
|
63
|
+
const { result } = await runEmbedWithFallback(async (model) => _embed({ model, value }), {
|
|
64
|
+
model: options.model ?? "embedding"
|
|
65
|
+
});
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
async function embedMany(values, options = {}) {
|
|
69
|
+
const { result } = await runEmbedWithFallback(async (model) => _embedMany({ model, values }), {
|
|
70
|
+
model: options.model ?? "embedding"
|
|
71
|
+
});
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
configure,
|
|
77
|
+
getConfig,
|
|
78
|
+
generateText,
|
|
79
|
+
streamText,
|
|
80
|
+
embed,
|
|
81
|
+
embedMany
|
|
82
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src/sdk/config.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var ProviderType = z.enum(["openrouter", "openai", "siliconflow", "gateway"]);
|
|
4
|
+
var NebutraAIConfigSchema = z.object({
|
|
5
|
+
/** Which provider backend to use. Defaults to "openrouter". */
|
|
6
|
+
provider: ProviderType.default("openrouter"),
|
|
7
|
+
/** API key override. Falls back to env vars per provider. */
|
|
8
|
+
apiKey: z.string().optional(),
|
|
9
|
+
/** Default model identifier. Provider-specific format. */
|
|
10
|
+
defaultModel: z.string().default("anthropic/claude-sonnet-4"),
|
|
11
|
+
/** Default temperature for generations. */
|
|
12
|
+
temperature: z.number().min(0).max(2).default(0.7),
|
|
13
|
+
/** Default max tokens for output. */
|
|
14
|
+
maxTokens: z.number().int().positive().optional(),
|
|
15
|
+
/** Extra headers merged into every request (e.g. HTTP-Referer for OpenRouter). */
|
|
16
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
17
|
+
/** Extra body fields merged into every request. */
|
|
18
|
+
extraBody: z.record(z.string(), z.unknown()).optional()
|
|
19
|
+
});
|
|
20
|
+
function resolveApiKey(config) {
|
|
21
|
+
if (config.apiKey) return config.apiKey;
|
|
22
|
+
const envMap = {
|
|
23
|
+
openrouter: "OPENROUTER_API_KEY",
|
|
24
|
+
openai: "OPENAI_API_KEY",
|
|
25
|
+
siliconflow: "SILICONFLOW_API_KEY",
|
|
26
|
+
gateway: "VERCEL_OIDC_TOKEN"
|
|
27
|
+
};
|
|
28
|
+
const envVar = envMap[config.provider];
|
|
29
|
+
const value = globalThis.process?.env?.[envVar];
|
|
30
|
+
if (!value) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`[@nebutra/agents] Missing API key. Set "${envVar}" in environment or pass "apiKey" in config.`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
ProviderType,
|
|
40
|
+
NebutraAIConfigSchema,
|
|
41
|
+
resolveApiKey
|
|
42
|
+
};
|