@shop-os/modelkit 0.1.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/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # @shop-os/modelkit
2
+
3
+ One catalog and one call path for every generative model ShopOS uses. A new model on an existing provider API is a catalog row. A new provider API is one adapter.
4
+
5
+ ## Use
6
+
7
+ ```ts
8
+ import { createDefaultModelKit } from "@shop-os/modelkit";
9
+
10
+ const kit = createDefaultModelKit({
11
+ routing: {
12
+ text: "cloudflare",
13
+ fal: "cloudflare-passthrough",
14
+ cloudflare: { accountId, gatewayId },
15
+ },
16
+ onUsage: (event) => usageClient.emit(event),
17
+ });
18
+
19
+ const image = await kit.generateImage(
20
+ { model: "nb-2", prompt, references: [{ url }], aspectRatio: "4:5", size: "2K" },
21
+ { tier: "enterprise" },
22
+ );
23
+
24
+ const job = await kit.generateVideo({
25
+ model: "seedance-2.0",
26
+ prompt,
27
+ firstFrame: { url },
28
+ durationSeconds: 8,
29
+ });
30
+ const video = await job.result({ timeoutMs: 15 * 60_000 });
31
+
32
+ const reply = kit.streamText({ model: "claude-sonnet-4-6", messages }); // or model: openai("gpt-4o")
33
+ for await (const chunk of reply.textStream) process.stdout.write(chunk);
34
+
35
+ const song = await kit.generateAudio({
36
+ model: "lyria-3-clip-preview",
37
+ prompt,
38
+ references: [{ url }],
39
+ });
40
+ const score = await kit.generateAudio({ model: "mmaudio", prompt, sourceVideo: { url } });
41
+
42
+ const clip = await kit.prepareMediaPart(
43
+ "gemini-2.5-flash",
44
+ { bytes, mime: "video/mp4" },
45
+ "video/mp4",
46
+ );
47
+ await kit.generateText({
48
+ model: "gemini-2.5-flash",
49
+ messages: [{ role: "user", content: [clip, { type: "text", text }] }],
50
+ });
51
+ await clip.cleanup?.();
52
+ ```
53
+
54
+ Keys come from `process.env` by default: bare names for pro (`OPENAI_API_KEY`, `FAL_KEY`, `ARK_API_KEY` ...) and the `ENTERPRISE_` prefix for enterprise. Pass `keys: { pro, enterprise }` to override.
55
+
56
+ ## A superset of `ai`
57
+
58
+ Every export of `ai` (the Vercel AI SDK) is re-exported here, so this import swap is
59
+ the whole migration:
60
+
61
+ ```ts
62
+ import {
63
+ generateText,
64
+ streamText,
65
+ generateObject,
66
+ tool,
67
+ jsonSchema,
68
+ Output,
69
+ } from "@shop-os/modelkit";
70
+ ```
71
+
72
+ The generation functions replace their `ai` namesakes with the same option and result
73
+ shapes. `model` is either a catalog id or alias, or the provider instance `ai` takes.
74
+ Both spellings work in every call:
75
+
76
+ ```ts
77
+ import { openai } from "@ai-sdk/openai";
78
+
79
+ await generateText({ model: "claude-sonnet-4-6", prompt }); // catalog: keys, routing, providerOptions, usage
80
+ await generateText({ model: openai("gpt-4o"), prompt }); // pass-through: the SDK runs it unchanged
81
+ ```
82
+
83
+ A pass-through model has no catalog row, so it gets no defaults and no key resolution,
84
+ and usage is attributed to its own `provider` and `modelId`. Use it for a model the
85
+ catalog does not carry yet; the next step is a catalog row.
86
+
87
+ | `ai` function | Here |
88
+ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
89
+ | `generateText`, `streamText` | Same. Catalog `providerOptions` are merged under the caller's; `prepareStep` model switches resolve through the catalog; usage per step. |
90
+ | `generateObject`, `streamObject` | Same, including `output: "array" \| "enum" \| "no-schema"`. |
91
+ | `generateImage` | Takes `n`, `size: "WxH"`, `aspectRatio`, `seed`, `abortSignal`, `providerOptions` plus the kit's `references`, `size: "2K"`, `quality`, `outputFormat`. Returns `image` and `images` with `uint8Array`, `base64`, `mediaType` next to `bytes`, `mime`. |
92
+ | `experimental_generateVideo` | The kit's `generateVideo` returns a job; `duration` is an alias of `durationSeconds`. An `ai` video model instance is wrapped as a settled job. The `ai` function is also re-exported as-is. |
93
+ | `experimental_generateSpeech`, `transcribe`, `embed`, `embedMany`, `rerank`, agents, UI streams | Re-exported from `ai` unchanged; the catalog has no rows for them. |
94
+
95
+ An optional second `CallContext` argument selects enterprise keys, adds cancellation
96
+ (combined with `abortSignal`), and sets `onUnsupported`. Package-level functions read
97
+ keys from the environment; a configured kit adds routing, catalog overrides, custom
98
+ fetch, logging, and the `onUsage` sink.
99
+
100
+ `providerOptions` on `generateImage`/`generateVideo` reach the provider only for
101
+ pass-through instances; catalog rows carry their own and the caller's are dropped
102
+ with an `option_ignored` warning.
103
+
104
+ The kit keeps its `Warning` type for coercions; the SDK's warning type is `AiWarning`.
105
+ `languageModel()` remains the low-level handle for Mastra and custom middleware.
106
+
107
+ ## What the package owns
108
+
109
+ | Concern | Rule |
110
+ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
111
+ | Ids | `id` is the pro-backend pricing key. Every historical id (`nb-pro`, `veo-3.1-generate-001`, `kling-v2.5-turbo-pro`) is an alias. |
112
+ | Options | `resolveOptions` fills catalog defaults, coerces aspect ratio to the nearest supported one, errors on unsupported size or resolution (`onUnsupported: "coerce"` snaps down instead), clamps duration, trims references (audio first, then video), and drops options the model ignores with a warning. |
113
+ | Keys | Enterprise keys win only when every key the adapter needs is set on the enterprise tier. |
114
+ | Routing | Cloudflare AI Gateway for text only. Never for Gemini images or Omni. fal passthrough for geo-blocked regions. OpenRouter optional for text. |
115
+ | Retry | One classifier. Never on content blocks or 4xx except 429. |
116
+ | Errors | `ModelKitError` with `code`, `provider`, `status`, `body`. Gemini `blockReason` and fal `body.detail` are lifted into `body`. |
117
+ | Usage | One event per call with the canonical model id. Video usage is emitted on completion. Audio: Lyria 3 returns no duration, so it emits `unit: "images", quantity: 1` (token counts ride along as `inputTokens`/`outputTokens`); Replicate audio (Lyria 2, MMAudio) emits `unit: "compute_seconds"` with the prediction time. |
118
+ | Media parts | `prepareMediaPart` turns a `MediaRef` into an AI SDK file part. Google inlines bytes up to 20 MB and uploads larger payloads through the Files API; call the returned `cleanup` after the request. Other providers get the reference inline. |
119
+
120
+ ## Add a model
121
+
122
+ 1. Add a row to `src/catalog/{image,video,text,other}.ts`. Copy `constraints` from the provider docs.
123
+ 2. Run `pnpm test`. The catalog test checks id and alias uniqueness.
124
+ 3. Bump `version` in `package.json`. Merging to `dev` publishes.
125
+
126
+ ## Adapters
127
+
128
+ `ai-sdk-text`, `google-image`, `openai-image`, `google-genai` (Veo, Omni, Lyria 3, Files API media parts), `ark` (Seedream, Seedance), `fal`, `replicate` (images, video, Lyria 2, MMAudio), `prodia`, `higgsfield`, `xai-video`. See `src/adapters/`.
129
+
130
+ ## Not in the package
131
+
132
+ Prompt construction, agent tools, node schemas and UI, S3 upload, credit reserve and settle, post-generation crop (needs `sharp`, caller side).
133
+
134
+ ## Live smoke test
135
+
136
+ `pnpm smoke [filter...]` runs one cheap real call per adapter (about $5 for the full matrix, 15 minutes, mostly video polling). Keys load from the `trigger-workflows`, `cowork`, and `vibe-triggers` env files next to this monorepo. Outputs land in `scripts/smoke-out/`. Run it before every release and after any adapter change. Never wire it into CI.