@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/src/gateway.ts DELETED
@@ -1,234 +0,0 @@
1
- import { isRetryableError } from "./fallback";
2
-
3
- export type AgentRuntimeGatewayMessageRole = "system" | "user" | "assistant" | "tool";
4
-
5
- export interface AgentRuntimeGatewayMessage {
6
- readonly role: AgentRuntimeGatewayMessageRole;
7
- readonly content: string;
8
- }
9
-
10
- export interface AgentRuntimeGatewayUsage {
11
- readonly inputTokens?: number;
12
- readonly outputTokens?: number;
13
- readonly totalTokens?: number;
14
- }
15
-
16
- export interface AgentRuntimeGatewayCompletion {
17
- readonly id: string;
18
- readonly provider: string;
19
- readonly model: string;
20
- readonly text: string;
21
- readonly usage?: AgentRuntimeGatewayUsage;
22
- readonly raw?: unknown;
23
- }
24
-
25
- export interface AgentRuntimeGatewayProvider {
26
- readonly id: string;
27
- readonly model: string;
28
- readonly capabilities: ReadonlySet<string>;
29
- complete(
30
- messages: readonly AgentRuntimeGatewayMessage[],
31
- options?: AgentRuntimeGatewayCompleteOptions,
32
- ): Promise<AgentRuntimeGatewayCompletion>;
33
- }
34
-
35
- export interface AgentRuntimeGatewayCompleteOptions {
36
- readonly temperature?: number;
37
- readonly maxTokens?: number;
38
- readonly signal?: AbortSignal;
39
- }
40
-
41
- export interface AgentRuntimeGatewayRequest extends AgentRuntimeGatewayCompleteOptions {
42
- readonly capability: string;
43
- readonly messages: readonly AgentRuntimeGatewayMessage[];
44
- readonly tenantId?: string;
45
- readonly userId?: string;
46
- readonly requestId?: string;
47
- readonly cacheKey?: string;
48
- readonly maxFallbacks?: number;
49
- }
50
-
51
- export interface AgentRuntimeGatewayDecision {
52
- readonly provider: string;
53
- readonly reason: string;
54
- readonly fallbackIndex: number;
55
- }
56
-
57
- export interface AgentRuntimeGatewayDebugEntry {
58
- readonly requestId: string;
59
- readonly tenantId?: string;
60
- readonly userId?: string;
61
- readonly decision: AgentRuntimeGatewayDecision;
62
- readonly ok: boolean;
63
- readonly error?: string;
64
- }
65
-
66
- export interface AgentRuntimeGatewayUsageReport {
67
- readonly calls: number;
68
- readonly inputTokens: number;
69
- readonly outputTokens: number;
70
- readonly totalTokens: number;
71
- readonly estimatedUsd: number;
72
- }
73
-
74
- export interface AgentRuntimeGatewayOptions {
75
- readonly providers: readonly AgentRuntimeGatewayProvider[];
76
- readonly estimateUsd?: (usage: Required<AgentRuntimeGatewayUsage>) => number;
77
- readonly requestId?: () => string;
78
- }
79
-
80
- interface CacheEntry {
81
- readonly response: AgentRuntimeGatewayCompletion;
82
- }
83
-
84
- function capabilityParts(capability: string): string[] {
85
- return capability
86
- .split(/[+,\s]+/)
87
- .map((part) => part.trim())
88
- .filter(Boolean);
89
- }
90
-
91
- function cacheKey(request: AgentRuntimeGatewayRequest): string {
92
- return (
93
- request.cacheKey ??
94
- JSON.stringify({
95
- capability: request.capability,
96
- prefix: request.messages.slice(0, Math.max(1, request.messages.length - 1)),
97
- last: request.messages.at(-1),
98
- })
99
- );
100
- }
101
-
102
- function normalizeUsage(
103
- usage: AgentRuntimeGatewayUsage | undefined,
104
- ): Required<AgentRuntimeGatewayUsage> {
105
- const inputTokens = usage?.inputTokens ?? 0;
106
- const outputTokens = usage?.outputTokens ?? 0;
107
- return {
108
- inputTokens,
109
- outputTokens,
110
- totalTokens: usage?.totalTokens ?? inputTokens + outputTokens,
111
- };
112
- }
113
-
114
- function defaultRequestId(): string {
115
- return `agents-gw-${Date.now()}-${Math.random().toString(16).slice(2)}`;
116
- }
117
-
118
- function defaultCostEstimate(usage: Required<AgentRuntimeGatewayUsage>): number {
119
- return usage.totalTokens * 0.000_001;
120
- }
121
-
122
- export class AgentRuntimeGateway {
123
- readonly #providers: readonly AgentRuntimeGatewayProvider[];
124
- readonly #cache = new Map<string, CacheEntry>();
125
- readonly #debug: AgentRuntimeGatewayDebugEntry[] = [];
126
- readonly #estimateUsd: (usage: Required<AgentRuntimeGatewayUsage>) => number;
127
- readonly #requestId: () => string;
128
- #hits = 0;
129
- #misses = 0;
130
- #usage: AgentRuntimeGatewayUsageReport = {
131
- calls: 0,
132
- inputTokens: 0,
133
- outputTokens: 0,
134
- totalTokens: 0,
135
- estimatedUsd: 0,
136
- };
137
-
138
- constructor(options: AgentRuntimeGatewayOptions) {
139
- this.#providers = options.providers;
140
- this.#estimateUsd = options.estimateUsd ?? defaultCostEstimate;
141
- this.#requestId = options.requestId ?? defaultRequestId;
142
- }
143
-
144
- async complete(request: AgentRuntimeGatewayRequest): Promise<AgentRuntimeGatewayCompletion> {
145
- const resolvedRequestId = request.requestId ?? this.#requestId();
146
- const resolvedCacheKey = cacheKey(request);
147
- const cached = this.#cache.get(resolvedCacheKey);
148
- if (cached) {
149
- this.#hits += 1;
150
- return cached.response;
151
- }
152
- this.#misses += 1;
153
-
154
- const providers = this.route(request);
155
- let lastError: unknown;
156
- const max = Math.min(request.maxFallbacks ?? providers.length, providers.length);
157
-
158
- for (let index = 0; index < max; index += 1) {
159
- const provider = providers[index];
160
- if (!provider) continue;
161
-
162
- const decision: AgentRuntimeGatewayDecision = {
163
- provider: provider.id,
164
- fallbackIndex: index,
165
- reason: `matched capability "${request.capability}"`,
166
- };
167
-
168
- try {
169
- const response = await provider.complete(request.messages, {
170
- ...(request.temperature !== undefined && { temperature: request.temperature }),
171
- ...(request.maxTokens !== undefined && { maxTokens: request.maxTokens }),
172
- ...(request.signal !== undefined && { signal: request.signal }),
173
- });
174
- this.#recordUsage(response);
175
- this.#cache.set(resolvedCacheKey, { response });
176
- this.#debug.push({
177
- requestId: resolvedRequestId,
178
- ...(request.tenantId !== undefined && { tenantId: request.tenantId }),
179
- ...(request.userId !== undefined && { userId: request.userId }),
180
- decision,
181
- ok: true,
182
- });
183
- return response;
184
- } catch (error) {
185
- lastError = error;
186
- this.#debug.push({
187
- requestId: resolvedRequestId,
188
- ...(request.tenantId !== undefined && { tenantId: request.tenantId }),
189
- ...(request.userId !== undefined && { userId: request.userId }),
190
- decision,
191
- ok: false,
192
- error: error instanceof Error ? error.message : String(error),
193
- });
194
-
195
- if (!isRetryableError(error)) throw error;
196
- }
197
- }
198
-
199
- throw new Error(
200
- `All AgentRuntimeGateway providers failed for capability "${request.capability}". Last error: ${String(lastError)}`,
201
- );
202
- }
203
-
204
- route(request: Pick<AgentRuntimeGatewayRequest, "capability">): AgentRuntimeGatewayProvider[] {
205
- const parts = capabilityParts(request.capability);
206
- const matched = this.#providers.filter((provider) =>
207
- parts.every((part) => provider.capabilities.has(part)),
208
- );
209
- return matched.length > 0 ? matched : [...this.#providers];
210
- }
211
-
212
- cacheStats(): { hits: number; misses: number; size: number } {
213
- return { hits: this.#hits, misses: this.#misses, size: this.#cache.size };
214
- }
215
-
216
- usageReport(): AgentRuntimeGatewayUsageReport {
217
- return { ...this.#usage };
218
- }
219
-
220
- debugLog(): readonly AgentRuntimeGatewayDebugEntry[] {
221
- return [...this.#debug];
222
- }
223
-
224
- #recordUsage(response: AgentRuntimeGatewayCompletion): void {
225
- const usage = normalizeUsage(response.usage);
226
- this.#usage = {
227
- calls: this.#usage.calls + 1,
228
- inputTokens: this.#usage.inputTokens + usage.inputTokens,
229
- outputTokens: this.#usage.outputTokens + usage.outputTokens,
230
- totalTokens: this.#usage.totalTokens + usage.totalTokens,
231
- estimatedUsd: Number((this.#usage.estimatedUsd + this.#estimateUsd(usage)).toFixed(6)),
232
- };
233
- }
234
- }
@@ -1,157 +0,0 @@
1
- /**
2
- * Image / video generation modality — public surface.
3
- *
4
- * Mirrors the LLM fallback design (`fallback.ts`): an ordered provider chain,
5
- * filtered to providers whose `envKey` is present, with `mock` as the
6
- * guaranteed terminal so a result is always produced. Retryable failures
7
- * (429 / 5xx / network) rotate to the next provider via `isRetryableError`.
8
- */
9
-
10
- import { logger } from "@nebutra/logger";
11
- import { isRetryableError } from "../fallback";
12
- import { mockGenerationProvider } from "./mock-provider";
13
- import type {
14
- GenerationCallOptions,
15
- GenerationContext,
16
- GenerationModality,
17
- GenerationProvider,
18
- GenerationResult,
19
- ImageGenerationRequest,
20
- VideoGenerationRequest,
21
- } from "./types";
22
-
23
- const log = logger.child({ module: "agents/generation" });
24
-
25
- // ── Registry ────────────────────────────────────────────────────────────────
26
- // `mock` is registered last and always available. Real providers register
27
- // ahead of it (additively) and win whenever their env key is present.
28
-
29
- const _registry = new Map<string, GenerationProvider>();
30
-
31
- export function registerGenerationProvider(provider: GenerationProvider): void {
32
- _registry.set(provider.name, provider);
33
- }
34
-
35
- /** Test helper — restores the registry to just the mock provider. */
36
- export function _resetGenerationRegistry(): void {
37
- _registry.clear();
38
- _registry.set(mockGenerationProvider.name, mockGenerationProvider);
39
- }
40
-
41
- _resetGenerationRegistry();
42
-
43
- function hasEnvKey(provider: GenerationProvider): boolean {
44
- if (provider.envKey === null) return true;
45
- return Boolean(globalThis.process?.env?.[provider.envKey]);
46
- }
47
-
48
- /** Provider names available for a modality, in resolved priority order. */
49
- export function listGenerationProviders(
50
- modality: GenerationModality,
51
- options: GenerationCallOptions = {},
52
- ): string[] {
53
- const envChain = (globalThis.process?.env?.GENERATION_FALLBACK_CHAIN ?? "")
54
- .split(",")
55
- .map((s) => s.trim())
56
- .filter(Boolean);
57
- const preferred = options.chain ?? (envChain.length > 0 ? envChain : []);
58
-
59
- // Real providers first (explicit chain wins, then registry order), `mock`
60
- // is always demoted to the guaranteed terminal regardless of registry order.
61
- const mockName = mockGenerationProvider.name;
62
- const all = [...preferred, ..._registry.keys()];
63
- const seen = new Set<string>();
64
- const resolved: string[] = [];
65
- for (const name of all) {
66
- if (seen.has(name) || name === mockName) continue;
67
- seen.add(name);
68
- const provider = _registry.get(name);
69
- if (!provider) continue;
70
- if (!provider.capabilities.includes(modality)) continue;
71
- if (!hasEnvKey(provider)) continue;
72
- resolved.push(name);
73
- }
74
- resolved.push(mockName);
75
- return resolved;
76
- }
77
-
78
- async function runChain(
79
- modality: GenerationModality,
80
- options: GenerationCallOptions,
81
- ctx: GenerationContext,
82
- invoke: (p: GenerationProvider) => Promise<GenerationResult>,
83
- ): Promise<GenerationResult> {
84
- const chain = listGenerationProviders(modality, options);
85
- let lastErr: unknown;
86
- for (const name of chain) {
87
- const provider = _registry.get(name);
88
- if (!provider) continue;
89
- try {
90
- const result = await invoke(provider);
91
- log.debug("generation succeeded", {
92
- provider: name,
93
- modality,
94
- tenantId: ctx.tenantId,
95
- });
96
- return result;
97
- } catch (err) {
98
- lastErr = err;
99
- const retryable = isRetryableError(err);
100
- log.warn("generation provider failed", {
101
- provider: name,
102
- modality,
103
- retryable,
104
- tenantId: ctx.tenantId,
105
- });
106
- // Non-retryable from the terminal mock would be a real bug — surface it.
107
- if (!retryable && name !== mockGenerationProvider.name) continue;
108
- if (!retryable) throw err;
109
- }
110
- }
111
- throw lastErr instanceof Error
112
- ? lastErr
113
- : new Error("[@nebutra/agents] generation chain exhausted");
114
- }
115
-
116
- /**
117
- * Generate an image. Always resolves (falls back to the deterministic mock).
118
- */
119
- export async function generateImage(
120
- req: ImageGenerationRequest,
121
- ctx: GenerationContext,
122
- options: GenerationCallOptions = {},
123
- ): Promise<GenerationResult> {
124
- return runChain("image", options, ctx, (p) => {
125
- if (!p.generateImage) {
126
- throw new Error(`[@nebutra/agents] provider "${p.name}" lacks image support`);
127
- }
128
- return p.generateImage(req, ctx);
129
- });
130
- }
131
-
132
- /**
133
- * Generate a video (or, in mock mode, a deterministic poster frame).
134
- */
135
- export async function generateVideo(
136
- req: VideoGenerationRequest,
137
- ctx: GenerationContext,
138
- options: GenerationCallOptions = {},
139
- ): Promise<GenerationResult> {
140
- return runChain("video", options, ctx, (p) => {
141
- if (!p.generateVideo) {
142
- throw new Error(`[@nebutra/agents] provider "${p.name}" lacks video support`);
143
- }
144
- return p.generateVideo(req, ctx);
145
- });
146
- }
147
-
148
- export { mockGenerationProvider } from "./mock-provider";
149
- export type {
150
- GenerationCallOptions,
151
- GenerationContext,
152
- GenerationModality,
153
- GenerationProvider,
154
- GenerationResult,
155
- ImageGenerationRequest,
156
- VideoGenerationRequest,
157
- } from "./types";
@@ -1,123 +0,0 @@
1
- /**
2
- * Deterministic mock generation provider.
3
- *
4
- * Always available (`envKey: null`) so CI and flag-gated demos never need a
5
- * paid secret. Output is a stable, content-addressed SVG `data:` URI: the same
6
- * prompt + size always yields byte-identical bytes, which makes canvas
7
- * placement and websocket-sync tests deterministic.
8
- *
9
- * Wiring a real provider (Replicate / OpenAI images / Volces) later is purely
10
- * additive — register it with a non-null `envKey` and it takes priority over
11
- * `mock` in the fallback chain whenever its key is present.
12
- */
13
-
14
- import type {
15
- GenerationContext,
16
- GenerationProvider,
17
- GenerationResult,
18
- ImageGenerationRequest,
19
- VideoGenerationRequest,
20
- } from "./types";
21
-
22
- /** FNV-1a — small, stable, no deps. Used to derive a deterministic hue. */
23
- function hash(input: string): number {
24
- let h = 0x811c9dc5;
25
- for (let i = 0; i < input.length; i++) {
26
- h ^= input.charCodeAt(i);
27
- h = Math.imul(h, 0x01000193);
28
- }
29
- return h >>> 0;
30
- }
31
-
32
- function escapeXml(s: string): string {
33
- return s
34
- .replace(/&/g, "&amp;")
35
- .replace(/</g, "&lt;")
36
- .replace(/>/g, "&gt;")
37
- .replace(/"/g, "&quot;");
38
- }
39
-
40
- function svgDataUri(label: string, prompt: string, w: number, h: number): string {
41
- const hue = hash(prompt) % 360;
42
- const hue2 = (hue + 40) % 360;
43
- // Wrap the prompt to ~32 chars/line, max 4 lines, so the placeholder
44
- // visibly carries its prompt (useful when eyeballing a canvas demo).
45
- const words = prompt.split(/\s+/);
46
- const lines: string[] = [];
47
- let cur = "";
48
- for (const word of words) {
49
- if ((cur + " " + word).trim().length > 32) {
50
- lines.push(cur.trim());
51
- cur = word;
52
- } else {
53
- cur = `${cur} ${word}`;
54
- }
55
- if (lines.length === 4) break;
56
- }
57
- if (cur && lines.length < 4) lines.push(cur.trim());
58
-
59
- const tspans = lines
60
- .map((ln, i) => `<tspan x="50%" dy="${i === 0 ? 0 : 26}">${escapeXml(ln)}</tspan>`)
61
- .join("");
62
-
63
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
64
- <defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
65
- <stop offset="0" stop-color="hsl(${hue} 70% 55%)"/>
66
- <stop offset="1" stop-color="hsl(${hue2} 70% 45%)"/>
67
- </linearGradient></defs>
68
- <rect width="${w}" height="${h}" fill="url(#g)"/>
69
- <text x="50%" y="14%" fill="rgba(255,255,255,.7)" font-family="sans-serif" font-size="20" text-anchor="middle">${escapeXml(label)}</text>
70
- <text x="50%" y="46%" fill="#fff" font-family="sans-serif" font-size="22" font-weight="600" text-anchor="middle">${tspans}</text>
71
- </svg>`;
72
-
73
- // base64 keeps the URI well-formed regardless of prompt characters.
74
- const b64 =
75
- typeof btoa === "function"
76
- ? btoa(unescape(encodeURIComponent(svg)))
77
- : Buffer.from(svg, "utf8").toString("base64");
78
- return `data:image/svg+xml;base64,${b64}`;
79
- }
80
-
81
- export const mockGenerationProvider: GenerationProvider = {
82
- name: "mock",
83
- envKey: null,
84
- capabilities: ["image", "video"],
85
-
86
- async generateImage(
87
- req: ImageGenerationRequest,
88
- _ctx: GenerationContext,
89
- ): Promise<GenerationResult> {
90
- const width = req.width ?? 1024;
91
- const height = req.height ?? 1024;
92
- return {
93
- modality: "image",
94
- mimeType: "image/svg+xml",
95
- url: svgDataUri("mock · image", req.prompt, width, height),
96
- width,
97
- height,
98
- providerName: "mock",
99
- model: req.model ?? "mock-image-1",
100
- usage: { units: 1 },
101
- };
102
- },
103
-
104
- async generateVideo(
105
- req: VideoGenerationRequest,
106
- _ctx: GenerationContext,
107
- ): Promise<GenerationResult> {
108
- const width = req.width ?? 1280;
109
- const height = req.height ?? 720;
110
- const seconds = req.durationSeconds ?? 5;
111
- // No real codec in mock mode — return a poster frame the canvas can embed.
112
- return {
113
- modality: "video",
114
- mimeType: "image/svg+xml",
115
- url: svgDataUri(`mock · video · ${seconds}s`, req.prompt, width, height),
116
- width,
117
- height,
118
- providerName: "mock",
119
- model: req.model ?? "mock-video-1",
120
- usage: { units: seconds },
121
- };
122
- },
123
- };
@@ -1,87 +0,0 @@
1
- /**
2
- * Image / video generation modality for `@nebutra/agents`.
3
- *
4
- * The text + embedding modalities wrap the Vercel AI SDK. Image / video
5
- * generation is a *new modality on the same provider layer*: providers are
6
- * env-key gated exactly like the LLM fallback chain (see `fallback.ts`), so
7
- * single-provider — or zero-provider (mock) — deploys just work.
8
- *
9
- * Generation is tenant-scoped: every call carries a {@link GenerationContext}
10
- * so downstream metering / audit can attribute units to an organization.
11
- */
12
-
13
- /** What a provider can produce. */
14
- export type GenerationModality = "image" | "video";
15
-
16
- /** Tenant-scoped attribution for a generation call (mirrors AgentContext). */
17
- export interface GenerationContext {
18
- readonly tenantId: string;
19
- readonly userId: string;
20
- /** Optional logical grouping (e.g. a canvas / conversation id). */
21
- readonly conversationId?: string;
22
- }
23
-
24
- export interface ImageGenerationRequest {
25
- readonly prompt: string;
26
- /** Pixel width — defaults to 1024. */
27
- readonly width?: number;
28
- /** Pixel height — defaults to 1024. */
29
- readonly height?: number;
30
- /** Optional model id / preset; provider-specific passthrough. */
31
- readonly model?: string;
32
- /** Reference images (data: URI or URL) for edit / variation flows. */
33
- readonly inputImages?: readonly string[];
34
- }
35
-
36
- export interface VideoGenerationRequest {
37
- readonly prompt: string;
38
- /** Clip length in seconds — defaults to 5. */
39
- readonly durationSeconds?: number;
40
- readonly width?: number;
41
- readonly height?: number;
42
- readonly model?: string;
43
- /** Optional first-frame image (data: URI or URL). */
44
- readonly inputImage?: string;
45
- }
46
-
47
- export interface GenerationResult {
48
- readonly modality: GenerationModality;
49
- /** e.g. "image/svg+xml", "image/png", "video/mp4". */
50
- readonly mimeType: string;
51
- /** `data:` URI (mock / inline) or a remote URL the caller can fetch. */
52
- readonly url: string;
53
- readonly width: number;
54
- readonly height: number;
55
- /** Provider that actually produced the asset. */
56
- readonly providerName: string;
57
- /** Model id reported by the provider. */
58
- readonly model: string;
59
- /**
60
- * Best-effort billable units for `@nebutra/metering` (e.g. 1 image,
61
- * N seconds of video). Callers decide the meter mapping.
62
- */
63
- readonly usage: { readonly units: number };
64
- }
65
-
66
- /**
67
- * A generation backend. `envKey` mirrors the LLM provider gating: when the
68
- * variable is absent the provider is filtered out of the chain. `null` means
69
- * "always available" — reserved for the deterministic mock provider so CI and
70
- * flag-gated demos never need a paid secret.
71
- */
72
- export interface GenerationProvider {
73
- readonly name: string;
74
- readonly envKey: string | null;
75
- readonly capabilities: readonly GenerationModality[];
76
- generateImage?(req: ImageGenerationRequest, ctx: GenerationContext): Promise<GenerationResult>;
77
- generateVideo?(req: VideoGenerationRequest, ctx: GenerationContext): Promise<GenerationResult>;
78
- }
79
-
80
- export interface GenerationCallOptions {
81
- /**
82
- * Ordered provider-name preference. Unknown / unavailable names are skipped.
83
- * Defaults to `GENERATION_FALLBACK_CHAIN` env (comma-separated) then registry
84
- * order, always ending at `mock` so a result is guaranteed.
85
- */
86
- readonly chain?: readonly string[];
87
- }