@reaatech/media-pipeline-mcp-provider-core 0.3.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 -0
- package/README.md +476 -0
- package/dist/index.cjs +529 -0
- package/dist/index.d.cts +262 -0
- package/dist/index.d.ts +262 -0
- package/dist/index.js +498 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Media Pipeline MCP Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# @reaatech/media-pipeline-mcp-provider-core
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@reaatech/media-pipeline-mcp-provider-core)
|
|
4
|
+
[](https://github.com/reaatech/media-pipeline-mcp/blob/main/LICENSE)
|
|
5
|
+
[](https://github.com/reaatech/media-pipeline-mcp/actions/workflows/ci.yml)
|
|
6
|
+
|
|
7
|
+
> **Status:** Pre-1.0 — APIs may change in minor versions. Pin to a specific version in production.
|
|
8
|
+
|
|
9
|
+
Abstract base class and shared interfaces for all media provider implementations. Defines the provider contract, deterministic caching (F2), retry with exponential backoff, cost estimation, and a multi-strategy router for provider selection across health, budget, and latency constraints.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @reaatech/media-pipeline-mcp-provider-core
|
|
15
|
+
# or
|
|
16
|
+
pnpm add @reaatech/media-pipeline-mcp-provider-core
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Feature Overview
|
|
20
|
+
|
|
21
|
+
- **Abstract `MediaProvider` class** — standardizes the interface for all media backends with required `name`, `supportedOperations`, `execute`, `estimateCost`, and optional `healthCheck`
|
|
22
|
+
- **Deterministic caching (F2)** — SHA-256 cache keys from provider::model::version::scopeTag::deterministic inputs; supports `use`, `refresh`, and `skip` modes with 30-day default TTL
|
|
23
|
+
- **Execute-with-retry** — exponential backoff with configurable `maxRetries`, `baseDelay`, `maxDelay`, and automatic non-retryable error detection
|
|
24
|
+
- **Cost estimation (F4/F5)** — `estimateCost` contract returning `{ costUsd, currency, breakdown, estimatedDurationMs }` per operation
|
|
25
|
+
- **Multi-strategy router (F8)** — `first-success` (sequential failover), `cheapest-acceptable` (cost-ordered with health/budget/queue gate), and `fastest` (race-to-first-complete with <5s duration cap)
|
|
26
|
+
- **Storage integration** — `setStorage` / `storeArtifact` helpers for persisting provider outputs
|
|
27
|
+
- **Webhook support (F7)** — `supportsWebhooks`, `webhookSignatureKey`, and `parseWebhookPayload` contracts for async provider callbacks
|
|
28
|
+
- **3D mesh generation types (F21)** — `MeshGenInput`, `MeshOutput`, `TextureConfig`, `MeshFormat` for 3D asset generation workflows
|
|
29
|
+
- **Canonical JSON normalization** — sorted-key, no-whitespace, no-trailing-zero serialization for deterministic cache keys
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { MediaProvider, defineProvider, type ProviderInput, type ProviderOutput } from "@reaatech/media-pipeline-mcp-provider-core";
|
|
35
|
+
|
|
36
|
+
class MyCustomProvider extends MediaProvider {
|
|
37
|
+
readonly name = "my-custom-provider";
|
|
38
|
+
readonly supportedOperations = ["image.generate", "image.upscale"];
|
|
39
|
+
|
|
40
|
+
constructor(private apiKey: string) {
|
|
41
|
+
super();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async healthCheck() {
|
|
45
|
+
try {
|
|
46
|
+
const response = await fetch("https://api.example.com/health");
|
|
47
|
+
return { healthy: response.ok, latency: 120 };
|
|
48
|
+
} catch {
|
|
49
|
+
return { healthy: false, error: "Connection refused" };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async estimateCost(input: ProviderInput) {
|
|
54
|
+
return { costUsd: 0.007, currency: "USD", estimatedDurationMs: 2000 };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async execute(input: ProviderInput): Promise<ProviderOutput> {
|
|
58
|
+
const response = await fetch("https://api.example.com/generate", {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: {
|
|
61
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
62
|
+
"Content-Type": "application/json",
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify({ prompt: input.params.prompt, width: 1024, height: 1024 }),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
data: buffer,
|
|
71
|
+
mimeType: "image/png",
|
|
72
|
+
metadata: { width: 1024, height: 1024, model: "v2" },
|
|
73
|
+
costUsd: 0.007,
|
|
74
|
+
durationMs: 1500,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API Reference
|
|
81
|
+
|
|
82
|
+
### `MediaProvider` (abstract class)
|
|
83
|
+
|
|
84
|
+
Base class that all provider implementations must extend. Provides caching, retry, and storage utilities.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
abstract class MediaProvider {
|
|
88
|
+
abstract readonly name: string;
|
|
89
|
+
abstract readonly supportedOperations: string[];
|
|
90
|
+
|
|
91
|
+
abstract estimateCost(input: ProviderInput): Promise<CostEstimate>;
|
|
92
|
+
abstract execute(input: ProviderInput): Promise<ProviderOutput>;
|
|
93
|
+
|
|
94
|
+
// Optional lifecycle
|
|
95
|
+
healthCheck?(): Promise<ProviderHealth>;
|
|
96
|
+
|
|
97
|
+
// Built-in helpers
|
|
98
|
+
setStorage(storage: ArtifactStore): void;
|
|
99
|
+
executeWithRetry(input: ProviderInput): Promise<ProviderOutput>;
|
|
100
|
+
executeWithCache(input: ProviderInput, cacheConfig?: CacheConfig): Promise<ProviderOutput>;
|
|
101
|
+
generateArtifactId(): string;
|
|
102
|
+
storeArtifact(data: Buffer | ReadableStream, type: ArtifactType, mimeType: string, metadata: Record<string, unknown>, sourceStep?: string): Promise<string>;
|
|
103
|
+
|
|
104
|
+
// Caching internals
|
|
105
|
+
protected computeCacheKey(input: ProviderInput, cacheConfig?: CacheConfig): string;
|
|
106
|
+
protected defaultCacheConfigForOperation(input: ProviderInput): CacheConfig;
|
|
107
|
+
protected canonicalJson(obj: unknown): string;
|
|
108
|
+
protected isNonRetryableError(error: unknown): boolean;
|
|
109
|
+
|
|
110
|
+
// Caching configuration
|
|
111
|
+
static cacheConfig: ProviderCacheConfig;
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `defineProvider`
|
|
116
|
+
|
|
117
|
+
Identity helper that passes through a provider class for registration.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
function defineProvider<T extends MediaProvider>(
|
|
121
|
+
providerClass: new (...args: unknown[]) => T,
|
|
122
|
+
): new (...args: unknown[]) => T;
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `ProviderInput`
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
interface ProviderInput {
|
|
129
|
+
operation: string;
|
|
130
|
+
params: Record<string, unknown>;
|
|
131
|
+
config: Record<string, unknown>;
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `ProviderOutput`
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
interface ProviderOutput {
|
|
139
|
+
data: Buffer | ReadableStream;
|
|
140
|
+
mimeType: string;
|
|
141
|
+
metadata: Record<string, unknown>;
|
|
142
|
+
costUsd?: number;
|
|
143
|
+
durationMs?: number;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### `ProviderHealth`
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
interface ProviderHealth {
|
|
151
|
+
healthy: boolean;
|
|
152
|
+
latency?: number;
|
|
153
|
+
error?: string;
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Cost Estimation Types
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
interface CostEstimate {
|
|
161
|
+
costUsd: number;
|
|
162
|
+
currency: string;
|
|
163
|
+
breakdown?: Array<{ component: string; costUsd: number }>;
|
|
164
|
+
estimatedDurationMs?: number;
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Caching (F2)
|
|
169
|
+
|
|
170
|
+
#### `CacheConfig`
|
|
171
|
+
|
|
172
|
+
| Property | Type | Default | Description |
|
|
173
|
+
|----------|------|---------|-------------|
|
|
174
|
+
| `mode` | `"use" \| "refresh" \| "skip"` | — | Cache mode: read-first, always-write, or bypass |
|
|
175
|
+
| `ttlSeconds` | `number` | `2592000` | Cache entry lifespan (30 days) |
|
|
176
|
+
| `scope` | `"global" \| "tenant"` | `"global"` | Key scoping: global or per-tenant |
|
|
177
|
+
|
|
178
|
+
#### `ProviderCacheConfig`
|
|
179
|
+
|
|
180
|
+
Per-provider static caching configuration controlling which params participate in cache keys.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
interface ProviderCacheConfig {
|
|
184
|
+
deterministicParams: string[]; // Only these params drive the cache key
|
|
185
|
+
nonDeterministicParams: string[]; // These params are excluded from the key
|
|
186
|
+
normalize: (inputs: Record<string, unknown>) => Record<string, unknown>;
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### Cache Key Formula
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
sha256(provider :: modelId :: modelVersion :: scopeTag :: operation :: canonicalInputs)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
- `modelId`: from `input.params.model` or `input.config.model`
|
|
197
|
+
- `modelVersion`: from `input.params.model_version` or `input.params.modelVersion`
|
|
198
|
+
- `scopeTag`: `"global"` or `"tenant:<tenantId>"`
|
|
199
|
+
- `canonicalInputs`: sorted-key JSON of deterministic params only
|
|
200
|
+
|
|
201
|
+
### Execute with Cache
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
class MyProvider extends MediaProvider {
|
|
205
|
+
async safeExecute(operation: string, input: ProviderInput) {
|
|
206
|
+
return this.executeWithCache(input, {
|
|
207
|
+
mode: "use",
|
|
208
|
+
ttlSeconds: 3600,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Cache modes:
|
|
215
|
+
- **`skip`** — bypass cache entirely, no read or write
|
|
216
|
+
- **`use`** — read from cache first; on miss, execute and store; cache hit rebates `costUsd` to 0
|
|
217
|
+
- **`refresh`** — always execute and overwrite the cache entry
|
|
218
|
+
|
|
219
|
+
### Execute with Retry
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
class MyProvider extends MediaProvider {
|
|
223
|
+
async robustExecute(input: ProviderInput) {
|
|
224
|
+
return this.executeWithRetry(input);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Built-in retry behavior:
|
|
230
|
+
- Up to 3 retries (configurable via `retryConfig`)
|
|
231
|
+
- Exponential backoff: `baseDelay × 2^(attempt-1)` capped at `maxDelay`
|
|
232
|
+
- Non-retryable errors detected by message patterns: `"authentication"`, `"unauthorized"`, `"validation"`, `"invalid api key"`
|
|
233
|
+
|
|
234
|
+
### Router (F8)
|
|
235
|
+
|
|
236
|
+
Multi-strategy provider routing for selecting among multiple provider/model candidates.
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
class Router {
|
|
240
|
+
constructor(ctx: RouterContext);
|
|
241
|
+
route(config: RouteConfig, inputs: ProviderInput): Promise<{ decision: RouteDecision; output: ProviderOutput }>;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### `RouterContext`
|
|
246
|
+
|
|
247
|
+
Injection interface the router uses to interact with the host.
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
interface RouterContext {
|
|
251
|
+
estimateCost(candidate: RouteCandidate, inputs: ProviderInput): Promise<CostEstimate>;
|
|
252
|
+
health(candidate: RouteCandidate): Promise<{ healthy: boolean; latencyMs?: number; queueDepth?: number }>;
|
|
253
|
+
execute(candidate: RouteCandidate, inputs: ProviderInput, signal: AbortSignal): Promise<ProviderOutput>;
|
|
254
|
+
expectedDurationMs?(candidate: RouteCandidate, inputs: ProviderInput): number | undefined;
|
|
255
|
+
queueMs?(candidate: RouteCandidate): Promise<number | undefined>;
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### `RouteConfig`
|
|
260
|
+
|
|
261
|
+
| Property | Type | Default | Description |
|
|
262
|
+
|----------|------|---------|-------------|
|
|
263
|
+
| `strategy` | `"first-success" \| "cheapest-acceptable" \| "fastest"` | — | Routing strategy |
|
|
264
|
+
| `candidates` | `RouteCandidate[]` | — | Provider/model candidates to route across |
|
|
265
|
+
| `timeoutMs` | `number` | — | Global timeout for the route attempt |
|
|
266
|
+
| `healthTtlMs` | `number` | `30000` | Health probe cache TTL |
|
|
267
|
+
|
|
268
|
+
#### `RouteCandidate`
|
|
269
|
+
|
|
270
|
+
| Property | Type | Description |
|
|
271
|
+
|----------|------|-------------|
|
|
272
|
+
| `provider` | `string` | Provider name |
|
|
273
|
+
| `model` | `string` | Model identifier |
|
|
274
|
+
| `maxQueueMs` | `number` | Maximum acceptable queue depth in ms |
|
|
275
|
+
| `maxUsd` | `number` | Maximum acceptable cost per call |
|
|
276
|
+
| `inputOverrides` | `Record<string, unknown>` | Per-candidate parameter overrides |
|
|
277
|
+
| `weight` | `number` | Tiebreaker weight for equal-cost candidates (default: 1) |
|
|
278
|
+
|
|
279
|
+
#### Routing Strategies
|
|
280
|
+
|
|
281
|
+
| Strategy | Behavior |
|
|
282
|
+
|----------|----------|
|
|
283
|
+
| `first-success` | Try candidates in order; return first successful result |
|
|
284
|
+
| `cheapest-acceptable` | Parallel health/estimate/queue probes; pick the cheapest healthy candidate under budget |
|
|
285
|
+
| `fastest` | Race all candidates simultaneously; return first to complete (all must be <5s expected duration) |
|
|
286
|
+
|
|
287
|
+
#### Router Errors
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
class RouterNoCandidatesError extends Error {
|
|
291
|
+
readonly code = "ROUTER_NO_CANDIDATES";
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
class RouterAllCandidatesFailedError extends Error {
|
|
295
|
+
readonly code = "ROUTER_ALL_CANDIDATES_FAILED";
|
|
296
|
+
readonly rejections: RouteRejection[];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
class RouterFastestIneligibleError extends Error {
|
|
300
|
+
readonly code = "ROUTER_FASTEST_INELIGIBLE";
|
|
301
|
+
readonly ineligibleCandidates: RouteCandidate[];
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### Rejection Reasons
|
|
306
|
+
|
|
307
|
+
| Reason | Trigger |
|
|
308
|
+
|--------|---------|
|
|
309
|
+
| `over-budget` | Candidate cost estimate exceeds `maxUsd` |
|
|
310
|
+
| `unhealthy` | Health check returned unhealthy |
|
|
311
|
+
| `queue-full` | Queue depth exceeds `maxQueueMs` |
|
|
312
|
+
| `error` | Execution error |
|
|
313
|
+
| `cancelled` | AbortSignal triggered (timeout or cancellation) |
|
|
314
|
+
| `fastest-ineligible` | Candidate exceeds 5s expected duration cap |
|
|
315
|
+
|
|
316
|
+
### Webhook Types (F7)
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
interface WebhookPayload {
|
|
320
|
+
jobId: string;
|
|
321
|
+
status: "completed" | "failed" | "progress";
|
|
322
|
+
output?: unknown;
|
|
323
|
+
pct?: number;
|
|
324
|
+
error?: { code: string; message: string };
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Mesh Generation Types (F21)
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
type MeshFormat = "glb" | "fbx" | "obj" | "usdz" | "ply";
|
|
332
|
+
|
|
333
|
+
interface MeshGenInput {
|
|
334
|
+
prompt?: string;
|
|
335
|
+
sourceArtifactId?: string;
|
|
336
|
+
format: MeshFormat;
|
|
337
|
+
polyBudget?: number;
|
|
338
|
+
topology?: "quads" | "tris";
|
|
339
|
+
texture?: TextureConfig;
|
|
340
|
+
animated?: boolean;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
interface TextureConfig {
|
|
344
|
+
enabled: boolean;
|
|
345
|
+
pbr?: boolean;
|
|
346
|
+
resolution?: 512 | 1024 | 2048 | 4096;
|
|
347
|
+
unwrap?: "auto" | "preserve-source";
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
interface MeshOutput {
|
|
351
|
+
artifactId: string;
|
|
352
|
+
format: MeshFormat;
|
|
353
|
+
polyCount: number;
|
|
354
|
+
hasTextures: boolean;
|
|
355
|
+
hasAnimation: boolean;
|
|
356
|
+
bboxMeters?: { x: number; y: number; z: number };
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### `MediaProviderLike`
|
|
361
|
+
|
|
362
|
+
Lightweight interface for provider shape checking and tooling.
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
interface MediaProviderLike {
|
|
366
|
+
readonly name: string;
|
|
367
|
+
readonly supportedOperations: string[];
|
|
368
|
+
estimateCost(input: ProviderInput): Promise<CostEstimate>;
|
|
369
|
+
execute(input: ProviderInput): Promise<ProviderOutput>;
|
|
370
|
+
healthCheck?(): Promise<ProviderHealth>;
|
|
371
|
+
supportsStreaming?: ReadonlySet<string>;
|
|
372
|
+
supportsWebhooks?: boolean;
|
|
373
|
+
webhookSignatureKey?(): Promise<string>;
|
|
374
|
+
parseWebhookPayload?(headers: Record<string, string>, body: string): Promise<WebhookPayload>;
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## Usage Patterns
|
|
379
|
+
|
|
380
|
+
### Implementing a Full Provider
|
|
381
|
+
|
|
382
|
+
Every provider must extend `MediaProvider` and implement:
|
|
383
|
+
|
|
384
|
+
1. `name` — unique provider identifier
|
|
385
|
+
2. `supportedOperations` — list of operations (e.g. `["image.generate", "image.upscale"]`)
|
|
386
|
+
3. `estimateCost(input)` → `CostEstimate` — per-operation cost from public pricing
|
|
387
|
+
4. `execute(input)` → `ProviderOutput` — main execution path with data, mimeType, metadata
|
|
388
|
+
5. `healthCheck()` → `ProviderHealth` — optional, used by router for health gating
|
|
389
|
+
|
|
390
|
+
### Customizing Cache Behavior
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
class OpenAIImageProvider extends MediaProvider {
|
|
394
|
+
readonly name = "openai-image";
|
|
395
|
+
readonly supportedOperations = ["image.generate"];
|
|
396
|
+
|
|
397
|
+
static cacheConfig: ProviderCacheConfig = {
|
|
398
|
+
deterministicParams: ["prompt", "size", "n"],
|
|
399
|
+
nonDeterministicParams: ["seed"],
|
|
400
|
+
normalize: (inputs) => {
|
|
401
|
+
const out: Record<string, unknown> = {};
|
|
402
|
+
for (const [k, v] of Object.entries(inputs)) {
|
|
403
|
+
out[k] = typeof v === "string" ? v.trim().replace(/\s+/g, " ") : v;
|
|
404
|
+
}
|
|
405
|
+
return out;
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
async estimateCost(input: ProviderInput) {
|
|
410
|
+
return { costUsd: 0.04, currency: "USD" };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async execute(input: ProviderInput): Promise<ProviderOutput> {
|
|
414
|
+
// ... implementation
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Router with Cheapest-Acceptable Strategy
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
const router = new Router({
|
|
423
|
+
estimateCost: async (candidate, inputs) => {
|
|
424
|
+
return { costUsd: candidate.provider === "stability" ? 0.007 : 0.04, currency: "USD" };
|
|
425
|
+
},
|
|
426
|
+
health: async (candidate) => ({
|
|
427
|
+
healthy: true,
|
|
428
|
+
latencyMs: candidate.model === "sd3" ? 1200 : 2000,
|
|
429
|
+
}),
|
|
430
|
+
execute: async (candidate, inputs, signal) => {
|
|
431
|
+
const provider = getProvider(candidate.provider);
|
|
432
|
+
return provider.execute(inputs);
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
const { decision, output } = await router.route(
|
|
437
|
+
{
|
|
438
|
+
strategy: "cheapest-acceptable",
|
|
439
|
+
candidates: [
|
|
440
|
+
{ provider: "stability", model: "sd3", maxUsd: 0.05 },
|
|
441
|
+
{ provider: "openai", model: "dall-e-3", maxUsd: 0.08 },
|
|
442
|
+
],
|
|
443
|
+
timeoutMs: 30000,
|
|
444
|
+
},
|
|
445
|
+
{ operation: "image.generate", params: { prompt: "sunset" }, config: {} },
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
console.log(decision.selected.provider); // "stability"
|
|
449
|
+
console.log(decision.reason); // "cheapest-acceptable: lowest cost among healthy candidates"
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Router with Fastest Strategy
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
const { decision, output } = await router.route(
|
|
456
|
+
{
|
|
457
|
+
strategy: "fastest",
|
|
458
|
+
candidates: [
|
|
459
|
+
{ provider: "fal", model: "flux-pro-1.1" },
|
|
460
|
+
{ provider: "replicate", model: "sdxl" },
|
|
461
|
+
],
|
|
462
|
+
},
|
|
463
|
+
{ operation: "image.generate", params: { prompt: "cat" }, config: {} },
|
|
464
|
+
);
|
|
465
|
+
// Both candidates are raced; the first to complete wins
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Related Packages
|
|
469
|
+
|
|
470
|
+
- [`@reaatech/media-pipeline-mcp-core`](https://www.npmjs.com/package/@reaatech/media-pipeline-mcp-core) — Core pipeline types consumed by providers
|
|
471
|
+
- [`@reaatech/media-pipeline-mcp-storage`](https://www.npmjs.com/package/@reaatech/media-pipeline-mcp-storage) — Artifact persistence used by `storeArtifact`
|
|
472
|
+
- [`@reaatech/media-pipeline-mcp-pipeline`](https://www.npmjs.com/package/@reaatech/media-pipeline-mcp-pipeline) — Pipeline templates and operations
|
|
473
|
+
|
|
474
|
+
## License
|
|
475
|
+
|
|
476
|
+
[MIT](https://github.com/reaatech/media-pipeline-mcp/blob/main/LICENSE)
|