@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.
Files changed (69) 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/{src/env.ts → dist/chunk-BMSL4E4A.js} +23 -47
  5. package/dist/{chunk-5JZJ5KMC.js → chunk-BZKIMAGK.js} +16 -7
  6. package/dist/{chunk-NPQECBXL.js → chunk-GRSMUTUS.js} +60 -7
  7. package/dist/{chunk-B7XWL35G.js → chunk-QYZDHC5A.js} +5 -1
  8. package/dist/{chunk-RLWM437Q.js → chunk-RNFUEMUB.js} +10 -3
  9. package/dist/chunk-RWQL4HXC.js +171 -0
  10. package/dist/{chunk-NVPE5EDI.js → chunk-V6VC2O6Q.js} +4 -3
  11. package/dist/chunk-VZPQOXWW.js +47 -0
  12. package/dist/{chunk-5LX742GP.js → chunk-XLBS3XUI.js} +4 -50
  13. package/dist/env.d.ts +42 -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 +21 -328
  20. package/dist/index.js +57 -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 +3 -0
  27. package/dist/sdk/config.js +1 -1
  28. package/dist/sdk/index.d.ts +36 -3
  29. package/dist/sdk/index.js +20 -7
  30. package/dist/sdk/models.d.ts +18 -6
  31. package/dist/sdk/models.js +1 -1
  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 +71 -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/src/__tests__/cost-observability.test.ts +0 -172
  43. package/src/__tests__/fallback-wiring.test.ts +0 -313
  44. package/src/__tests__/generation.test.ts +0 -111
  45. package/src/__tests__/public-api.test.ts +0 -114
  46. package/src/__tests__/runtime-gateway.test.ts +0 -108
  47. package/src/agent.ts +0 -117
  48. package/src/context.ts +0 -99
  49. package/src/fallback.ts +0 -358
  50. package/src/gateway.ts +0 -234
  51. package/src/generation/index.ts +0 -157
  52. package/src/generation/mock-provider.ts +0 -123
  53. package/src/generation/types.ts +0 -87
  54. package/src/index.ts +0 -104
  55. package/src/memory.ts +0 -126
  56. package/src/observability.ts +0 -102
  57. package/src/orchestrator.ts +0 -147
  58. package/src/providers/langchain.ts +0 -28
  59. package/src/providers/vercel-ai.ts +0 -114
  60. package/src/router.ts +0 -158
  61. package/src/sdk/config.ts +0 -73
  62. package/src/sdk/index.ts +0 -214
  63. package/src/sdk/models.ts +0 -57
  64. package/src/sdk/provider.ts +0 -80
  65. package/src/tenant.ts +0 -52
  66. package/src/tools.ts +0 -65
  67. package/src/types.ts +0 -114
  68. package/tsconfig.json +0 -12
  69. 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,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"]);
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
+ };
@@ -1,17 +1,17 @@
1
1
  // src/sdk/models.ts
2
2
  var models = {
3
3
  /** High-quality reasoning — default for complex tasks */
4
- flagship: "anthropic/claude-sonnet-4",
4
+ flagship: "anthropic/claude-sonnet-4.6",
5
5
  /** Deep reasoning for architecture and research */
6
- reasoning: "anthropic/claude-opus-4",
6
+ reasoning: "anthropic/claude-opus-4.8",
7
7
  /** Fast + cheap — chat, summaries, classification */
8
- fast: "anthropic/claude-haiku-4",
8
+ fast: "anthropic/claude-haiku-4.5",
9
9
  /** OpenAI flagship */
10
- "openai-flagship": "openai/gpt-5.4",
10
+ "openai-flagship": "openai/gpt-5.5",
11
11
  /** Google flagship */
12
- "google-flagship": "google/gemini-2.5-pro",
12
+ "google-flagship": "google/gemini-3.1-pro-preview",
13
13
  /** Google fast */
14
- "google-fast": "google/gemini-2.5-flash",
14
+ "google-fast": "google/gemini-3.5-flash",
15
15
  /** Embedding model */
16
16
  embedding: "openai/text-embedding-3-small",
17
17
  /** Embedding model (high-dimensional) */
@@ -22,7 +22,16 @@ var models = {
22
22
  /** SiliconFlow — DeepSeek R1 reasoning (via SiliconFlow Pro) */
23
23
  "sf-deepseek-r1": "Pro/deepseek-ai/DeepSeek-R1",
24
24
  /** SiliconFlow — DeepSeek V3 (fast, capable) */
25
- "sf-deepseek-v3": "deepseek-ai/DeepSeek-V3"
25
+ "sf-deepseek-v3": "deepseek-ai/DeepSeek-V3",
26
+ // --- SenseNova Token Plan presets (use with provider: "sensenova") ---
27
+ // Base URL: https://token.sensenova.cn/v1
28
+ // Docs: https://github.com/OpenSenseNova/SenseNova6.7/blob/main/API_CN.md
29
+ /** SenseNova 6.7 Flash-Lite — small multimodal agent (default for i18n CI) */
30
+ "sn-flash-lite": "sensenova-6.7-flash-lite",
31
+ /** DeepSeek V4 Flash via SenseNova Token Plan — long context, cheap */
32
+ "sn-deepseek-flash": "deepseek-v4-flash",
33
+ /** Alias: prefer flash-lite for bulk translation */
34
+ "sn-translate": "sensenova-6.7-flash-lite"
26
35
  };
27
36
  function resolveModel(modelOrPreset) {
28
37
  if (modelOrPreset in models) {
@@ -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-RNFUEMUB.js";
7
4
  import {
8
5
  NebutraAIConfigSchema
9
- } from "./chunk-NVPE5EDI.js";
6
+ } from "./chunk-V6VC2O6Q.js";
7
+ import {
8
+ assertSafeOpenAIJsonPayload
9
+ } from "./chunk-VZPQOXWW.js";
10
+ import {
11
+ runEmbedWithFallback,
12
+ runWithFallback
13
+ } from "./chunk-XLBS3XUI.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,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getAgentsEnv,
3
3
  isLangfuseConfigured
4
- } from "./chunk-5LX742GP.js";
4
+ } from "./chunk-BMSL4E4A.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,9 +1,9 @@
1
1
  import {
2
2
  resolveApiKey
3
- } from "./chunk-NVPE5EDI.js";
3
+ } from "./chunk-V6VC2O6Q.js";
4
4
  import {
5
5
  resolveModel
6
- } from "./chunk-5JZJ5KMC.js";
6
+ } from "./chunk-BZKIMAGK.js";
7
7
 
8
8
  // src/sdk/provider.ts
9
9
  import { createOpenAI } from "@ai-sdk/openai";
@@ -27,7 +27,14 @@ function createModel(modelOrPreset, config) {
27
27
  case "siliconflow": {
28
28
  const provider = createOpenAI({
29
29
  apiKey,
30
- baseURL: "https://api.siliconflow.cn/v1"
30
+ baseURL: process.env.SILICONFLOW_BASE_URL ?? "https://api.siliconflow.cn/v1"
31
+ });
32
+ return provider(modelId);
33
+ }
34
+ case "sensenova": {
35
+ const provider = createOpenAI({
36
+ apiKey,
37
+ baseURL: process.env.SENSENOVA_BASE_URL ?? "https://token.sensenova.cn/v1"
31
38
  });
32
39
  return provider(modelId);
33
40
  }
@@ -0,0 +1,171 @@
1
+ import {
2
+ isRetryableError
3
+ } from "./chunk-XLBS3XUI.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,13 +1,13 @@
1
1
  // src/sdk/config.ts
2
2
  import { z } from "zod";
3
- var ProviderType = z.enum(["openrouter", "openai", "siliconflow", "gateway"]);
3
+ var ProviderType = z.enum(["openrouter", "openai", "siliconflow", "sensenova", "gateway"]);
4
4
  var NebutraAIConfigSchema = z.object({
5
5
  /** Which provider backend to use. Defaults to "openrouter". */
6
6
  provider: ProviderType.default("openrouter"),
7
7
  /** API key override. Falls back to env vars per provider. */
8
8
  apiKey: z.string().optional(),
9
- /** Default model identifier. Provider-specific format. */
10
- defaultModel: z.string().default("anthropic/claude-sonnet-4"),
9
+ /** Default model id — OpenRouter / models.dev frontier (not Claude 3.x / GPT-4 era). */
10
+ defaultModel: z.string().default("anthropic/claude-sonnet-4.6"),
11
11
  /** Default temperature for generations. */
12
12
  temperature: z.number().min(0).max(2).default(0.7),
13
13
  /** Default max tokens for output. */
@@ -23,6 +23,7 @@ function resolveApiKey(config) {
23
23
  openrouter: "OPENROUTER_API_KEY",
24
24
  openai: "OPENAI_API_KEY",
25
25
  siliconflow: "SILICONFLOW_API_KEY",
26
+ sensenova: "SENSENOVA_API_KEY",
26
27
  gateway: "VERCEL_OIDC_TOKEN"
27
28
  };
28
29
  const envVar = envMap[config.provider];
@@ -0,0 +1,47 @@
1
+ // src/sdk/payload-guard.ts
2
+ var OPENAI_MAX_PROPERTY_NAME_LENGTH = 256;
3
+ function isObjectLike(value) {
4
+ return typeof value === "object" && value !== null;
5
+ }
6
+ function pathSegment(key) {
7
+ const preview = key.length > 32 ? `${key.slice(0, 29)}...` : key;
8
+ return /^[A-Za-z_$][\w$]*$/.test(preview) ? `.${preview}` : `[${JSON.stringify(preview)}]`;
9
+ }
10
+ function findOversizedPropertyName(value, maxLength = OPENAI_MAX_PROPERTY_NAME_LENGTH) {
11
+ const seen = /* @__PURE__ */ new WeakSet();
12
+ function visit(candidate, path) {
13
+ if (!isObjectLike(candidate)) return null;
14
+ if (seen.has(candidate)) return null;
15
+ seen.add(candidate);
16
+ if (Array.isArray(candidate)) {
17
+ for (let index = 0; index < candidate.length; index += 1) {
18
+ const issue = visit(candidate[index], `${path}[${index}]`);
19
+ if (issue) return issue;
20
+ }
21
+ return null;
22
+ }
23
+ for (const [key, nested] of Object.entries(candidate)) {
24
+ const nestedPath = `${path}${pathSegment(key)}`;
25
+ if (key.length > maxLength) {
26
+ return { path: nestedPath, length: key.length };
27
+ }
28
+ const issue = visit(nested, nestedPath);
29
+ if (issue) return issue;
30
+ }
31
+ return null;
32
+ }
33
+ return visit(value, "$");
34
+ }
35
+ function assertSafeOpenAIJsonPayload(label, value) {
36
+ const issue = findOversizedPropertyName(value);
37
+ if (!issue) return;
38
+ throw new Error(
39
+ `${label} contains an object property name at ${issue.path} that is ${issue.length} characters long; maximum is ${OPENAI_MAX_PROPERTY_NAME_LENGTH}. Put generated text in a string value instead of using it as a JSON key.`
40
+ );
41
+ }
42
+
43
+ export {
44
+ OPENAI_MAX_PROPERTY_NAME_LENGTH,
45
+ findOversizedPropertyName,
46
+ assertSafeOpenAIJsonPayload
47
+ };
@@ -1,52 +1,9 @@
1
1
  import {
2
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
- }
3
+ } from "./chunk-BZKIMAGK.js";
4
+ import {
5
+ getAgentsEnv
6
+ } from "./chunk-BMSL4E4A.js";
50
7
 
51
8
  // src/fallback.ts
52
9
  import { logger } from "@nebutra/logger";
@@ -212,9 +169,6 @@ async function runEmbedWithFallback(invoke, options = {}) {
212
169
  }
213
170
 
214
171
  export {
215
- AgentsEnvSchema,
216
- getAgentsEnv,
217
- isLangfuseConfigured,
218
172
  filterAvailableProviders,
219
173
  isRetryableError,
220
174
  runWithFallback,