@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.
Files changed (70) hide show
  1. package/LICENSE +21 -676
  2. package/README.md +7 -3
  3. package/dist/{agent-CahnMASx.d.ts → agent-DDGWuUpe.d.ts} +1 -1
  4. package/dist/{chunk-B7XWL35G.js → chunk-7VL333VJ.js} +5 -1
  5. package/dist/{chunk-5LX742GP.js → chunk-C4CC5UEF.js} +25 -53
  6. package/{src/env.ts → dist/chunk-FCXWOXII.js} +23 -47
  7. package/dist/chunk-HVLFZW6E.js +71 -0
  8. package/dist/chunk-HZQXXUKB.js +171 -0
  9. package/dist/{chunk-RLWM437Q.js → chunk-KC6SOI5Z.js} +19 -4
  10. package/dist/{chunk-NPQECBXL.js → chunk-S5N743EP.js} +60 -7
  11. package/dist/chunk-VZPQOXWW.js +47 -0
  12. package/dist/{chunk-NVPE5EDI.js → chunk-YBCIJKC7.js} +20 -3
  13. package/dist/env.d.ts +45 -0
  14. package/dist/env.js +12 -0
  15. package/dist/fallback.d.ts +100 -0
  16. package/dist/fallback.js +18 -0
  17. package/dist/generation/index.d.ts +122 -0
  18. package/dist/generation/index.js +19 -0
  19. package/dist/index.d.ts +22 -329
  20. package/dist/index.js +59 -245
  21. package/dist/observability.d.ts +46 -0
  22. package/dist/observability.js +13 -0
  23. package/dist/providers/langchain.d.ts +2 -2
  24. package/dist/providers/vercel-ai.d.ts +2 -2
  25. package/dist/providers/vercel-ai.js +15 -7
  26. package/dist/sdk/config.d.ts +6 -0
  27. package/dist/sdk/config.js +2 -1
  28. package/dist/sdk/index.d.ts +37 -4
  29. package/dist/sdk/index.js +23 -8
  30. package/dist/sdk/models.d.ts +20 -27
  31. package/dist/sdk/models.js +5 -3
  32. package/dist/sdk/provider.d.ts +1 -0
  33. package/dist/sdk/provider.js +3 -3
  34. package/dist/tools.d.ts +1 -1
  35. package/dist/{types-NtgB3pch.d.ts → types-BytC-HfQ.d.ts} +1 -5
  36. package/package.json +73 -19
  37. package/.turbo/turbo-build.log +0 -40
  38. package/.turbo/turbo-test.log +0 -19
  39. package/.turbo/turbo-typecheck.log +0 -4
  40. package/AGENTS.md +0 -63
  41. package/CHANGELOG.md +0 -36
  42. package/dist/chunk-5JZJ5KMC.js +0 -37
  43. package/src/__tests__/cost-observability.test.ts +0 -172
  44. package/src/__tests__/fallback-wiring.test.ts +0 -313
  45. package/src/__tests__/generation.test.ts +0 -111
  46. package/src/__tests__/public-api.test.ts +0 -114
  47. package/src/__tests__/runtime-gateway.test.ts +0 -108
  48. package/src/agent.ts +0 -117
  49. package/src/context.ts +0 -99
  50. package/src/fallback.ts +0 -358
  51. package/src/gateway.ts +0 -234
  52. package/src/generation/index.ts +0 -157
  53. package/src/generation/mock-provider.ts +0 -123
  54. package/src/generation/types.ts +0 -87
  55. package/src/index.ts +0 -104
  56. package/src/memory.ts +0 -126
  57. package/src/observability.ts +0 -102
  58. package/src/orchestrator.ts +0 -147
  59. package/src/providers/langchain.ts +0 -28
  60. package/src/providers/vercel-ai.ts +0 -114
  61. package/src/router.ts +0 -158
  62. package/src/sdk/config.ts +0 -73
  63. package/src/sdk/index.ts +0 -214
  64. package/src/sdk/models.ts +0 -57
  65. package/src/sdk/provider.ts +0 -80
  66. package/src/tenant.ts +0 -52
  67. package/src/tools.ts +0 -65
  68. package/src/types.ts +0 -114
  69. package/tsconfig.json +0 -12
  70. 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-4", instructions: "..." },
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-4", instructions: "..." }),
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-NtgB3pch.js';
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-5LX742GP.js";
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
- } 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
- }
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
- /** Comma-separated provider chain. Order = priority. */
12
- const FallbackProviderName = z.enum(["openrouter", "anthropic", "openai"]);
13
- export type FallbackProviderName = z.infer<typeof FallbackProviderName>;
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((): FallbackProviderName[] => [
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((): FallbackProviderName[] => [
31
+ LLM_EMBEDDING_FALLBACK_CHAIN: FallbackChain.default(() => [
53
32
  "openrouter",
54
- "openai",
55
- ]),
33
+ "openai"
34
+ ])
56
35
  });
57
-
58
- export type AgentsEnv = z.infer<typeof AgentsEnvSchema>;
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
- /** Test helper — clears cache so updated process.env is picked up. */
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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-NVPE5EDI.js";
3
+ } from "./chunk-YBCIJKC7.js";
4
4
  import {
5
- resolveModel
6
- } from "./chunk-5JZJ5KMC.js";
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-RLWM437Q.js";
3
+ } from "./chunk-KC6SOI5Z.js";
7
4
  import {
8
5
  NebutraAIConfigSchema
9
- } from "./chunk-NVPE5EDI.js";
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 _generateText,
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 _generateText({
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,