@nebutra/agents 1.1.1 → 2.0.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/LICENSE +21 -676
- package/README.md +7 -3
- package/dist/{agent-CahnMASx.d.ts → agent-DDGWuUpe.d.ts} +1 -1
- package/dist/{chunk-B7XWL35G.js → chunk-7VL333VJ.js} +5 -1
- package/dist/{chunk-5LX742GP.js → chunk-C4CC5UEF.js} +25 -53
- package/{src/env.ts → dist/chunk-FCXWOXII.js} +23 -47
- package/dist/chunk-HVLFZW6E.js +71 -0
- package/dist/chunk-HZQXXUKB.js +171 -0
- package/dist/{chunk-RLWM437Q.js → chunk-KC6SOI5Z.js} +19 -4
- package/dist/{chunk-NPQECBXL.js → chunk-S5N743EP.js} +60 -7
- package/dist/chunk-VZPQOXWW.js +47 -0
- package/dist/{chunk-NVPE5EDI.js → chunk-YBCIJKC7.js} +20 -3
- package/dist/env.d.ts +45 -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 +22 -329
- package/dist/index.js +59 -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 +6 -0
- package/dist/sdk/config.js +2 -1
- package/dist/sdk/index.d.ts +37 -4
- package/dist/sdk/index.js +23 -8
- package/dist/sdk/models.d.ts +20 -27
- package/dist/sdk/models.js +5 -3
- 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 +73 -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/dist/chunk-5JZJ5KMC.js +0 -37
- 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/README.md
CHANGED
|
@@ -41,7 +41,7 @@ integration hook lives here in `providers/langchain.ts` as an extension point.
|
|
|
41
41
|
```ts
|
|
42
42
|
import { configure, streamText } from "@nebutra/agents";
|
|
43
43
|
|
|
44
|
-
configure({ provider: "openrouter", defaultModel: "anthropic/claude-sonnet-4" });
|
|
44
|
+
configure({ provider: "openrouter", defaultModel: "anthropic/claude-sonnet-4.6" });
|
|
45
45
|
|
|
46
46
|
const result = await streamText(
|
|
47
47
|
[{ role: "user", content: "Explain monorepos" }],
|
|
@@ -58,13 +58,13 @@ import { VercelAIAgent } from "@nebutra/agents/providers/vercel-ai";
|
|
|
58
58
|
|
|
59
59
|
const orchestrator = new AgentOrchestrator({
|
|
60
60
|
agents: [
|
|
61
|
-
{ id: "assistant", name: "Assistant", description: "Helpful", model: "openai/gpt-
|
|
61
|
+
{ id: "assistant", name: "Assistant", description: "Helpful", model: "openai/gpt-5.5", instructions: "..." },
|
|
62
62
|
],
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
// Swap the BaseAgent for a real VercelAIAgent at runtime:
|
|
66
66
|
orchestrator.registerAgent(
|
|
67
|
-
new VercelAIAgent({ id: "assistant", name: "Assistant", description: "Helpful", model: "openai/gpt-
|
|
67
|
+
new VercelAIAgent({ id: "assistant", name: "Assistant", description: "Helpful", model: "openai/gpt-5.5", instructions: "..." }),
|
|
68
68
|
);
|
|
69
69
|
|
|
70
70
|
const ctx = createAgentContext("org_123", "user_456");
|
|
@@ -76,3 +76,7 @@ const response = await orchestrator.chat("Hello", ctx);
|
|
|
76
76
|
|
|
77
77
|
Every agent operation requires a `tenantId`. Usage events are emitted for
|
|
78
78
|
billing and metering integration (see `@nebutra/billing/credits`).
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as AgentConfig, A as AgentMessage, a as AgentContext, b as AgentResponse } from './types-
|
|
1
|
+
import { c as AgentConfig, A as AgentMessage, a as AgentContext, b as AgentResponse } from './types-BytC-HfQ.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* BaseAgent — abstract agent with memory, usage tracking, and tenant scoping.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getAgentsEnv,
|
|
3
3
|
isLangfuseConfigured
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-FCXWOXII.js";
|
|
5
5
|
|
|
6
6
|
// src/observability.ts
|
|
7
7
|
import { logger } from "@nebutra/logger";
|
|
@@ -28,6 +28,9 @@ async function initLangfuse() {
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
function _resetLangfuseCache() {
|
|
32
|
+
_client = void 0;
|
|
33
|
+
}
|
|
31
34
|
function buildTelemetryConfig(args) {
|
|
32
35
|
if (!isLangfuseConfigured()) {
|
|
33
36
|
return { isEnabled: false };
|
|
@@ -55,6 +58,7 @@ async function flushTelemetry() {
|
|
|
55
58
|
|
|
56
59
|
export {
|
|
57
60
|
initLangfuse,
|
|
61
|
+
_resetLangfuseCache,
|
|
58
62
|
buildTelemetryConfig,
|
|
59
63
|
flushTelemetry
|
|
60
64
|
};
|
|
@@ -1,60 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
|
-
resolveModel
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
}
|
|
2
|
+
resolveModel,
|
|
3
|
+
toAi302ModelId
|
|
4
|
+
} from "./chunk-HVLFZW6E.js";
|
|
5
|
+
import {
|
|
6
|
+
getAgentsEnv
|
|
7
|
+
} from "./chunk-FCXWOXII.js";
|
|
50
8
|
|
|
51
9
|
// src/fallback.ts
|
|
52
10
|
import { logger } from "@nebutra/logger";
|
|
53
11
|
var ENV_KEY_BY_PROVIDER = {
|
|
54
12
|
openrouter: "OPENROUTER_API_KEY",
|
|
55
13
|
anthropic: "ANTHROPIC".concat("_API_KEY"),
|
|
56
|
-
openai: "OPENAI".concat("_API_KEY")
|
|
14
|
+
openai: "OPENAI".concat("_API_KEY"),
|
|
15
|
+
ai302: "AI302_API_KEY"
|
|
57
16
|
};
|
|
17
|
+
function ai302BaseUrl() {
|
|
18
|
+
return globalThis.process?.env?.AI302_BASE_URL ?? "https://api.302.ai/v1";
|
|
19
|
+
}
|
|
58
20
|
function hasProviderKey(provider) {
|
|
59
21
|
const k = ENV_KEY_BY_PROVIDER[provider];
|
|
60
22
|
return Boolean(globalThis.process?.env?.[k]);
|
|
@@ -83,6 +45,10 @@ async function buildModel(provider, modelOrPreset) {
|
|
|
83
45
|
const openaiModelId = modelId.startsWith("openai/") ? modelId.slice("openai/".length) : modelId;
|
|
84
46
|
return createOpenAI({ apiKey })(openaiModelId);
|
|
85
47
|
}
|
|
48
|
+
case "ai302": {
|
|
49
|
+
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
50
|
+
return createOpenAI({ apiKey, baseURL: ai302BaseUrl() })(toAi302ModelId(modelId));
|
|
51
|
+
}
|
|
86
52
|
}
|
|
87
53
|
}
|
|
88
54
|
var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
@@ -145,7 +111,10 @@ function buildSystemWithCache(systemPrompt) {
|
|
|
145
111
|
}
|
|
146
112
|
var EMBEDDING_CAPABLE = /* @__PURE__ */ new Set([
|
|
147
113
|
"openrouter",
|
|
148
|
-
"openai"
|
|
114
|
+
"openai",
|
|
115
|
+
// 302.AI serves /v1/embeddings — an unauthenticated POST answers
|
|
116
|
+
// `Missing 302 Apikey` rather than 404, so the route exists behind auth.
|
|
117
|
+
"ai302"
|
|
149
118
|
]);
|
|
150
119
|
async function buildEmbeddingModel(provider, modelOrPreset) {
|
|
151
120
|
const modelId = resolveModel(modelOrPreset);
|
|
@@ -162,6 +131,12 @@ async function buildEmbeddingModel(provider, modelOrPreset) {
|
|
|
162
131
|
const openaiModelId = modelId.startsWith("openai/") ? modelId.slice("openai/".length) : modelId;
|
|
163
132
|
return createOpenAI({ apiKey }).textEmbeddingModel(openaiModelId);
|
|
164
133
|
}
|
|
134
|
+
case "ai302": {
|
|
135
|
+
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
136
|
+
return createOpenAI({ apiKey, baseURL: ai302BaseUrl() }).textEmbeddingModel(
|
|
137
|
+
toAi302ModelId(modelId)
|
|
138
|
+
);
|
|
139
|
+
}
|
|
165
140
|
case "anthropic": {
|
|
166
141
|
throw new Error("Anthropic does not expose embedding models");
|
|
167
142
|
}
|
|
@@ -212,9 +187,6 @@ async function runEmbedWithFallback(invoke, options = {}) {
|
|
|
212
187
|
}
|
|
213
188
|
|
|
214
189
|
export {
|
|
215
|
-
AgentsEnvSchema,
|
|
216
|
-
getAgentsEnv,
|
|
217
|
-
isLangfuseConfigured,
|
|
218
190
|
filterAvailableProviders,
|
|
219
191
|
isRetryableError,
|
|
220
192
|
runWithFallback,
|
|
@@ -1,79 +1,55 @@
|
|
|
1
|
-
|
|
2
|
-
* Environment validation for `@nebutra/agents`.
|
|
3
|
-
*
|
|
4
|
-
* All variables are OPTIONAL — the package must work with zero new env config.
|
|
5
|
-
* Provider keys (OPENROUTER_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.)
|
|
6
|
-
* are validated lazily by the provider resolver, not here.
|
|
7
|
-
*/
|
|
8
|
-
|
|
1
|
+
// src/env.ts
|
|
9
2
|
import { z } from "zod";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const FallbackChain = z
|
|
16
|
-
.string()
|
|
17
|
-
.transform((raw) =>
|
|
18
|
-
raw
|
|
19
|
-
.split(",")
|
|
20
|
-
.map((s) => s.trim())
|
|
21
|
-
.filter(Boolean),
|
|
22
|
-
)
|
|
23
|
-
.pipe(z.array(FallbackProviderName).min(1));
|
|
24
|
-
|
|
25
|
-
export const AgentsEnvSchema = z.object({
|
|
3
|
+
var FallbackProviderName = z.enum(["openrouter", "anthropic", "openai", "ai302"]);
|
|
4
|
+
var FallbackChain = z.string().transform(
|
|
5
|
+
(raw) => raw.split(",").map((s) => s.trim()).filter(Boolean)
|
|
6
|
+
).pipe(z.array(FallbackProviderName).min(1));
|
|
7
|
+
var AgentsEnvSchema = z.object({
|
|
26
8
|
// ── Anthropic (direct) ─────────────────────────────────────────────────
|
|
27
9
|
ANTHROPIC_API_KEY: z.string().optional(),
|
|
28
|
-
|
|
29
10
|
// ── Langfuse (LLM tracing — optional) ─────────────────────────────────
|
|
30
11
|
LANGFUSE_PUBLIC_KEY: z.string().optional(),
|
|
31
12
|
LANGFUSE_SECRET_KEY: z.string().optional(),
|
|
32
13
|
LANGFUSE_HOST: z.string().url().default("https://cloud.langfuse.com"),
|
|
33
|
-
|
|
34
14
|
// ── Multi-provider fallback chain ─────────────────────────────────────
|
|
35
15
|
/**
|
|
36
16
|
* Comma-separated chain of providers tried in order on retryable failures.
|
|
37
17
|
* Default: "openrouter,anthropic,openai" — OpenRouter first (multi-model),
|
|
38
18
|
* then direct Anthropic (prompt caching), then direct OpenAI as last resort.
|
|
39
19
|
*/
|
|
40
|
-
LLM_FALLBACK_CHAIN: FallbackChain.default(()
|
|
20
|
+
LLM_FALLBACK_CHAIN: FallbackChain.default(() => [
|
|
41
21
|
"openrouter",
|
|
42
22
|
"anthropic",
|
|
43
|
-
"openai"
|
|
23
|
+
"openai"
|
|
44
24
|
]),
|
|
45
|
-
|
|
46
25
|
/**
|
|
47
26
|
* Comma-separated chain of providers tried for EMBEDDINGS, in order.
|
|
48
27
|
* Default: "openrouter,openai" — Anthropic does not currently expose
|
|
49
28
|
* embedding models, so it is excluded by default. If unset, falls back
|
|
50
29
|
* to LLM_FALLBACK_CHAIN with embedding-incompatible providers filtered out.
|
|
51
30
|
*/
|
|
52
|
-
LLM_EMBEDDING_FALLBACK_CHAIN: FallbackChain.default(()
|
|
31
|
+
LLM_EMBEDDING_FALLBACK_CHAIN: FallbackChain.default(() => [
|
|
53
32
|
"openrouter",
|
|
54
|
-
"openai"
|
|
55
|
-
])
|
|
33
|
+
"openai"
|
|
34
|
+
])
|
|
56
35
|
});
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
/** Lazy parsed env — re-read once on first call. */
|
|
61
|
-
let _cached: AgentsEnv | undefined;
|
|
62
|
-
|
|
63
|
-
/** Returns the validated env (cached). Safe to call from any runtime. */
|
|
64
|
-
export function getAgentsEnv(): AgentsEnv {
|
|
36
|
+
var _cached;
|
|
37
|
+
function getAgentsEnv() {
|
|
65
38
|
if (_cached) return _cached;
|
|
66
39
|
_cached = AgentsEnvSchema.parse(globalThis.process?.env ?? {});
|
|
67
40
|
return _cached;
|
|
68
41
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
export function _resetAgentsEnvCache(): void {
|
|
72
|
-
_cached = undefined;
|
|
42
|
+
function _resetAgentsEnvCache() {
|
|
43
|
+
_cached = void 0;
|
|
73
44
|
}
|
|
74
|
-
|
|
75
|
-
/** True iff Langfuse credentials are present. */
|
|
76
|
-
export function isLangfuseConfigured(): boolean {
|
|
45
|
+
function isLangfuseConfigured() {
|
|
77
46
|
const env = getAgentsEnv();
|
|
78
47
|
return Boolean(env.LANGFUSE_PUBLIC_KEY && env.LANGFUSE_SECRET_KEY);
|
|
79
48
|
}
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
AgentsEnvSchema,
|
|
52
|
+
getAgentsEnv,
|
|
53
|
+
_resetAgentsEnvCache,
|
|
54
|
+
isLangfuseConfigured
|
|
55
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/sdk/frontier-fallback.generated.ts
|
|
2
|
+
var FRONTIER_FALLBACK = {
|
|
3
|
+
reasoning: "anthropic/claude-opus-5",
|
|
4
|
+
flagship: "anthropic/claude-sonnet-5",
|
|
5
|
+
fast: "anthropic/claude-haiku-4.5",
|
|
6
|
+
"openai-flagship": "openai/gpt-5.6-sol",
|
|
7
|
+
"google-flagship": "google/gemini-3.1-pro-preview",
|
|
8
|
+
"google-fast": "google/gemini-3.7-flash"
|
|
9
|
+
};
|
|
10
|
+
var AI302_ALIASES = {
|
|
11
|
+
fast: "claude-haiku-4-5-20251001"
|
|
12
|
+
};
|
|
13
|
+
var AI302_OPEN_MODELS = {
|
|
14
|
+
"302-deepseek": "deepseek-v4-pro",
|
|
15
|
+
"302-deepseek-fast": "deepseek-v4-flash",
|
|
16
|
+
"302-qwen": "qwen3.8-max",
|
|
17
|
+
"302-glm": "glm-5.3",
|
|
18
|
+
"302-kimi": "kimi-k3",
|
|
19
|
+
"302-minimax": "MiniMax-M3"
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// src/sdk/models.ts
|
|
23
|
+
var models = {
|
|
24
|
+
// ── Generated frontier tiers — edit via `pnpm gen:frontier-models` ──────────
|
|
25
|
+
...FRONTIER_FALLBACK,
|
|
26
|
+
/** Embedding model */
|
|
27
|
+
embedding: "openai/text-embedding-3-small",
|
|
28
|
+
/** Embedding model (high-dimensional) */
|
|
29
|
+
"embedding-large": "openai/text-embedding-3-large",
|
|
30
|
+
// --- Open-weight families via 302.AI (use with provider: "ai302") ---
|
|
31
|
+
// Generated with the tiers above. These replaced three SiliconFlow presets
|
|
32
|
+
// naming Qwen2.5-72B, DeepSeek-R1 and DeepSeek-V3 — every one superseded,
|
|
33
|
+
// none with a caller anywhere in the repo, and none checkable without a
|
|
34
|
+
// SiliconFlow key. 302 serves the same families and lists its catalogue,
|
|
35
|
+
// so these are resolved rather than remembered.
|
|
36
|
+
...AI302_OPEN_MODELS,
|
|
37
|
+
// --- SenseNova Token Plan presets (use with provider: "sensenova") ---
|
|
38
|
+
// Base URL: https://token.sensenova.cn/v1
|
|
39
|
+
// Docs: https://github.com/OpenSenseNova/SenseNova6.7/blob/main/API_CN.md
|
|
40
|
+
/** SenseNova 6.7 Flash-Lite — small multimodal agent (default for i18n CI) */
|
|
41
|
+
"sn-flash-lite": "sensenova-6.7-flash-lite",
|
|
42
|
+
/** DeepSeek V4 Flash via SenseNova Token Plan — long context, cheap */
|
|
43
|
+
"sn-deepseek-flash": "deepseek-v4-flash",
|
|
44
|
+
/** Alias: prefer flash-lite for bulk translation */
|
|
45
|
+
"sn-translate": "sensenova-6.7-flash-lite"
|
|
46
|
+
};
|
|
47
|
+
function resolveModel(modelOrPreset) {
|
|
48
|
+
if (modelOrPreset in models) {
|
|
49
|
+
return models[modelOrPreset];
|
|
50
|
+
}
|
|
51
|
+
return modelOrPreset;
|
|
52
|
+
}
|
|
53
|
+
var AI302_BY_GATEWAY_ID = Object.fromEntries(
|
|
54
|
+
Object.entries(AI302_ALIASES).map(([tier, id]) => [
|
|
55
|
+
FRONTIER_FALLBACK[tier],
|
|
56
|
+
id
|
|
57
|
+
])
|
|
58
|
+
);
|
|
59
|
+
function toAi302ModelId(modelOrPreset) {
|
|
60
|
+
const modelId = resolveModel(modelOrPreset);
|
|
61
|
+
const alias = AI302_BY_GATEWAY_ID[modelId];
|
|
62
|
+
if (alias) return alias;
|
|
63
|
+
const slash = modelId.indexOf("/");
|
|
64
|
+
return slash === -1 ? modelId : modelId.slice(slash + 1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
models,
|
|
69
|
+
resolveModel,
|
|
70
|
+
toAi302ModelId
|
|
71
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isRetryableError
|
|
3
|
+
} from "./chunk-C4CC5UEF.js";
|
|
4
|
+
|
|
5
|
+
// src/generation/index.ts
|
|
6
|
+
import { logger } from "@nebutra/logger";
|
|
7
|
+
|
|
8
|
+
// src/generation/mock-provider.ts
|
|
9
|
+
function hash(input) {
|
|
10
|
+
let h = 2166136261;
|
|
11
|
+
for (let i = 0; i < input.length; i++) {
|
|
12
|
+
h ^= input.charCodeAt(i);
|
|
13
|
+
h = Math.imul(h, 16777619);
|
|
14
|
+
}
|
|
15
|
+
return h >>> 0;
|
|
16
|
+
}
|
|
17
|
+
function escapeXml(s) {
|
|
18
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
19
|
+
}
|
|
20
|
+
function svgDataUri(label, prompt, w, h) {
|
|
21
|
+
const hue = hash(prompt) % 360;
|
|
22
|
+
const hue2 = (hue + 40) % 360;
|
|
23
|
+
const words = prompt.split(/\s+/);
|
|
24
|
+
const lines = [];
|
|
25
|
+
let cur = "";
|
|
26
|
+
for (const word of words) {
|
|
27
|
+
if ((cur + " " + word).trim().length > 32) {
|
|
28
|
+
lines.push(cur.trim());
|
|
29
|
+
cur = word;
|
|
30
|
+
} else {
|
|
31
|
+
cur = `${cur} ${word}`;
|
|
32
|
+
}
|
|
33
|
+
if (lines.length === 4) break;
|
|
34
|
+
}
|
|
35
|
+
if (cur && lines.length < 4) lines.push(cur.trim());
|
|
36
|
+
const tspans = lines.map((ln, i) => `<tspan x="50%" dy="${i === 0 ? 0 : 26}">${escapeXml(ln)}</tspan>`).join("");
|
|
37
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
|
|
38
|
+
<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
39
|
+
<stop offset="0" stop-color="hsl(${hue} 70% 55%)"/>
|
|
40
|
+
<stop offset="1" stop-color="hsl(${hue2} 70% 45%)"/>
|
|
41
|
+
</linearGradient></defs>
|
|
42
|
+
<rect width="${w}" height="${h}" fill="url(#g)"/>
|
|
43
|
+
<text x="50%" y="14%" fill="rgba(255,255,255,.7)" font-family="sans-serif" font-size="20" text-anchor="middle">${escapeXml(label)}</text>
|
|
44
|
+
<text x="50%" y="46%" fill="#fff" font-family="sans-serif" font-size="22" font-weight="600" text-anchor="middle">${tspans}</text>
|
|
45
|
+
</svg>`;
|
|
46
|
+
const b64 = typeof btoa === "function" ? btoa(unescape(encodeURIComponent(svg))) : Buffer.from(svg, "utf8").toString("base64");
|
|
47
|
+
return `data:image/svg+xml;base64,${b64}`;
|
|
48
|
+
}
|
|
49
|
+
var mockGenerationProvider = {
|
|
50
|
+
name: "mock",
|
|
51
|
+
envKey: null,
|
|
52
|
+
capabilities: ["image", "video"],
|
|
53
|
+
async generateImage(req, _ctx) {
|
|
54
|
+
const width = req.width ?? 1024;
|
|
55
|
+
const height = req.height ?? 1024;
|
|
56
|
+
return {
|
|
57
|
+
modality: "image",
|
|
58
|
+
mimeType: "image/svg+xml",
|
|
59
|
+
url: svgDataUri("mock \xB7 image", req.prompt, width, height),
|
|
60
|
+
width,
|
|
61
|
+
height,
|
|
62
|
+
providerName: "mock",
|
|
63
|
+
model: req.model ?? "mock-image-1",
|
|
64
|
+
usage: { units: 1 }
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
async generateVideo(req, _ctx) {
|
|
68
|
+
const width = req.width ?? 1280;
|
|
69
|
+
const height = req.height ?? 720;
|
|
70
|
+
const seconds = req.durationSeconds ?? 5;
|
|
71
|
+
return {
|
|
72
|
+
modality: "video",
|
|
73
|
+
mimeType: "image/svg+xml",
|
|
74
|
+
url: svgDataUri(`mock \xB7 video \xB7 ${seconds}s`, req.prompt, width, height),
|
|
75
|
+
width,
|
|
76
|
+
height,
|
|
77
|
+
providerName: "mock",
|
|
78
|
+
model: req.model ?? "mock-video-1",
|
|
79
|
+
usage: { units: seconds }
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// src/generation/index.ts
|
|
85
|
+
var log = logger.child({ module: "agents/generation" });
|
|
86
|
+
var _registry = /* @__PURE__ */ new Map();
|
|
87
|
+
function registerGenerationProvider(provider) {
|
|
88
|
+
_registry.set(provider.name, provider);
|
|
89
|
+
}
|
|
90
|
+
function _resetGenerationRegistry() {
|
|
91
|
+
_registry.clear();
|
|
92
|
+
_registry.set(mockGenerationProvider.name, mockGenerationProvider);
|
|
93
|
+
}
|
|
94
|
+
_resetGenerationRegistry();
|
|
95
|
+
function hasEnvKey(provider) {
|
|
96
|
+
if (provider.envKey === null) return true;
|
|
97
|
+
return Boolean(globalThis.process?.env?.[provider.envKey]);
|
|
98
|
+
}
|
|
99
|
+
function listGenerationProviders(modality, options = {}) {
|
|
100
|
+
const envChain = (globalThis.process?.env?.GENERATION_FALLBACK_CHAIN ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
101
|
+
const preferred = options.chain ?? (envChain.length > 0 ? envChain : []);
|
|
102
|
+
const mockName = mockGenerationProvider.name;
|
|
103
|
+
const all = [...preferred, ..._registry.keys()];
|
|
104
|
+
const seen = /* @__PURE__ */ new Set();
|
|
105
|
+
const resolved = [];
|
|
106
|
+
for (const name of all) {
|
|
107
|
+
if (seen.has(name) || name === mockName) continue;
|
|
108
|
+
seen.add(name);
|
|
109
|
+
const provider = _registry.get(name);
|
|
110
|
+
if (!provider) continue;
|
|
111
|
+
if (!provider.capabilities.includes(modality)) continue;
|
|
112
|
+
if (!hasEnvKey(provider)) continue;
|
|
113
|
+
resolved.push(name);
|
|
114
|
+
}
|
|
115
|
+
resolved.push(mockName);
|
|
116
|
+
return resolved;
|
|
117
|
+
}
|
|
118
|
+
async function runChain(modality, options, ctx, invoke) {
|
|
119
|
+
const chain = listGenerationProviders(modality, options);
|
|
120
|
+
let lastErr;
|
|
121
|
+
for (const name of chain) {
|
|
122
|
+
const provider = _registry.get(name);
|
|
123
|
+
if (!provider) continue;
|
|
124
|
+
try {
|
|
125
|
+
const result = await invoke(provider);
|
|
126
|
+
log.debug("generation succeeded", {
|
|
127
|
+
provider: name,
|
|
128
|
+
modality,
|
|
129
|
+
tenantId: ctx.tenantId
|
|
130
|
+
});
|
|
131
|
+
return result;
|
|
132
|
+
} catch (err) {
|
|
133
|
+
lastErr = err;
|
|
134
|
+
const retryable = isRetryableError(err);
|
|
135
|
+
log.warn("generation provider failed", {
|
|
136
|
+
provider: name,
|
|
137
|
+
modality,
|
|
138
|
+
retryable,
|
|
139
|
+
tenantId: ctx.tenantId
|
|
140
|
+
});
|
|
141
|
+
if (!retryable && name !== mockGenerationProvider.name) continue;
|
|
142
|
+
if (!retryable) throw err;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
throw lastErr instanceof Error ? lastErr : new Error("[@nebutra/agents] generation chain exhausted");
|
|
146
|
+
}
|
|
147
|
+
async function generateImage(req, ctx, options = {}) {
|
|
148
|
+
return runChain("image", options, ctx, (p) => {
|
|
149
|
+
if (!p.generateImage) {
|
|
150
|
+
throw new Error(`[@nebutra/agents] provider "${p.name}" lacks image support`);
|
|
151
|
+
}
|
|
152
|
+
return p.generateImage(req, ctx);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
async function generateVideo(req, ctx, options = {}) {
|
|
156
|
+
return runChain("video", options, ctx, (p) => {
|
|
157
|
+
if (!p.generateVideo) {
|
|
158
|
+
throw new Error(`[@nebutra/agents] provider "${p.name}" lacks video support`);
|
|
159
|
+
}
|
|
160
|
+
return p.generateVideo(req, ctx);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export {
|
|
165
|
+
mockGenerationProvider,
|
|
166
|
+
registerGenerationProvider,
|
|
167
|
+
_resetGenerationRegistry,
|
|
168
|
+
listGenerationProviders,
|
|
169
|
+
generateImage,
|
|
170
|
+
generateVideo
|
|
171
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveApiKey
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-YBCIJKC7.js";
|
|
4
4
|
import {
|
|
5
|
-
resolveModel
|
|
6
|
-
|
|
5
|
+
resolveModel,
|
|
6
|
+
toAi302ModelId
|
|
7
|
+
} from "./chunk-HVLFZW6E.js";
|
|
7
8
|
|
|
8
9
|
// src/sdk/provider.ts
|
|
9
10
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
@@ -27,10 +28,24 @@ function createModel(modelOrPreset, config) {
|
|
|
27
28
|
case "siliconflow": {
|
|
28
29
|
const provider = createOpenAI({
|
|
29
30
|
apiKey,
|
|
30
|
-
baseURL: "https://api.siliconflow.cn/v1"
|
|
31
|
+
baseURL: process.env.SILICONFLOW_BASE_URL ?? "https://api.siliconflow.cn/v1"
|
|
31
32
|
});
|
|
32
33
|
return provider(modelId);
|
|
33
34
|
}
|
|
35
|
+
case "sensenova": {
|
|
36
|
+
const provider = createOpenAI({
|
|
37
|
+
apiKey,
|
|
38
|
+
baseURL: process.env.SENSENOVA_BASE_URL ?? "https://token.sensenova.cn/v1"
|
|
39
|
+
});
|
|
40
|
+
return provider(modelId);
|
|
41
|
+
}
|
|
42
|
+
case "ai302": {
|
|
43
|
+
const provider = createOpenAI({
|
|
44
|
+
apiKey,
|
|
45
|
+
baseURL: process.env.AI302_BASE_URL ?? "https://api.302.ai/v1"
|
|
46
|
+
});
|
|
47
|
+
return provider(toAi302ModelId(modelId));
|
|
48
|
+
}
|
|
34
49
|
case "gateway": {
|
|
35
50
|
const provider = createOpenAI({
|
|
36
51
|
apiKey,
|
|
@@ -1,20 +1,69 @@
|
|
|
1
|
-
import {
|
|
2
|
-
runEmbedWithFallback
|
|
3
|
-
} from "./chunk-5LX742GP.js";
|
|
4
1
|
import {
|
|
5
2
|
createModel
|
|
6
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-KC6SOI5Z.js";
|
|
7
4
|
import {
|
|
8
5
|
NebutraAIConfigSchema
|
|
9
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YBCIJKC7.js";
|
|
7
|
+
import {
|
|
8
|
+
assertSafeOpenAIJsonPayload
|
|
9
|
+
} from "./chunk-VZPQOXWW.js";
|
|
10
|
+
import {
|
|
11
|
+
runEmbedWithFallback,
|
|
12
|
+
runWithFallback
|
|
13
|
+
} from "./chunk-C4CC5UEF.js";
|
|
10
14
|
|
|
11
15
|
// src/sdk/index.ts
|
|
12
16
|
import {
|
|
13
17
|
embed as _embed,
|
|
14
18
|
embedMany as _embedMany,
|
|
15
|
-
generateText as
|
|
19
|
+
generateText as _generateText2,
|
|
16
20
|
streamText as _streamText
|
|
17
21
|
} from "ai";
|
|
22
|
+
|
|
23
|
+
// src/sdk/structured.ts
|
|
24
|
+
import { generateText as _generateText, jsonSchema, tool } from "ai";
|
|
25
|
+
import Ajv from "ajv";
|
|
26
|
+
var ajv = new Ajv({ allErrors: true, strict: false });
|
|
27
|
+
function validateStructured(value, schema) {
|
|
28
|
+
const validate = ajv.compile(schema);
|
|
29
|
+
if (!validate(value)) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`structured output failed schema validation: ${ajv.errorsText(validate.errors)}`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function generateStructured(messages, schema, options = {}) {
|
|
36
|
+
assertSafeOpenAIJsonPayload("OpenAI structured request messages", messages);
|
|
37
|
+
assertSafeOpenAIJsonPayload("OpenAI structured output schema", schema);
|
|
38
|
+
const { result } = await runWithFallback(
|
|
39
|
+
(model) => _generateText({
|
|
40
|
+
model,
|
|
41
|
+
messages,
|
|
42
|
+
tools: {
|
|
43
|
+
_output: tool({
|
|
44
|
+
description: "Return the final result as a structured object matching the schema.",
|
|
45
|
+
inputSchema: jsonSchema(schema)
|
|
46
|
+
})
|
|
47
|
+
},
|
|
48
|
+
toolChoice: { type: "tool", toolName: "_output" }
|
|
49
|
+
}),
|
|
50
|
+
{ model: options.model ?? "flagship" }
|
|
51
|
+
);
|
|
52
|
+
const call = result.toolCalls?.find((c) => c.toolName === "_output");
|
|
53
|
+
const output = call?.input ?? null;
|
|
54
|
+
validateStructured(output, schema);
|
|
55
|
+
const usage = result.usage;
|
|
56
|
+
return {
|
|
57
|
+
output,
|
|
58
|
+
usage: {
|
|
59
|
+
inputTokens: usage?.inputTokens ?? 0,
|
|
60
|
+
outputTokens: usage?.outputTokens ?? 0,
|
|
61
|
+
reasoningOutputTokens: usage?.reasoningTokens ?? 0
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/sdk/index.ts
|
|
18
67
|
var _resolved = NebutraAIConfigSchema.parse({});
|
|
19
68
|
function configure(config = {}) {
|
|
20
69
|
_resolved = NebutraAIConfigSchema.parse(config);
|
|
@@ -23,8 +72,9 @@ function getConfig() {
|
|
|
23
72
|
return _resolved;
|
|
24
73
|
}
|
|
25
74
|
async function generateText(messages, options = {}) {
|
|
75
|
+
assertSafeOpenAIJsonPayload("OpenAI request messages", messages);
|
|
26
76
|
const model = createModel(options.model ?? _resolved.defaultModel, _resolved);
|
|
27
|
-
return await
|
|
77
|
+
return await _generateText2({
|
|
28
78
|
model,
|
|
29
79
|
messages,
|
|
30
80
|
...options.system ? { system: options.system } : {},
|
|
@@ -34,6 +84,7 @@ async function generateText(messages, options = {}) {
|
|
|
34
84
|
});
|
|
35
85
|
}
|
|
36
86
|
async function streamText(messages, options = {}) {
|
|
87
|
+
assertSafeOpenAIJsonPayload("OpenAI request messages", messages);
|
|
37
88
|
const model = createModel(options.model ?? _resolved.defaultModel, _resolved);
|
|
38
89
|
const userOnFinish = options.onFinish;
|
|
39
90
|
return _streamText({
|
|
@@ -73,6 +124,8 @@ async function embedMany(values, options = {}) {
|
|
|
73
124
|
}
|
|
74
125
|
|
|
75
126
|
export {
|
|
127
|
+
validateStructured,
|
|
128
|
+
generateStructured,
|
|
76
129
|
configure,
|
|
77
130
|
getConfig,
|
|
78
131
|
generateText,
|