@brotu/ai 0.4.0 → 0.5.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 CHANGED
@@ -23,7 +23,7 @@
23
23
  <a href="https://x.com/brotuApp">@brotuApp</a>
24
24
  </p>
25
25
 
26
- Kling, Seedance, Wan, Veo, Gemini, gpt-image and ElevenLabs all speak different HTTP. This package is one `brotuClient` in front of them.
26
+ Kling, Seedance, Wan, Veo, Gemini, gpt-image and ElevenLabs all speak different HTTP. This package is one `brotu` in front of them.
27
27
 
28
28
  Get your key (`brotu_sk_…`) at [brotu.app](https://brotu.app). Pass a vendor key you already have and that model hits the vendor. Everything else generates on Brotu.
29
29
 
@@ -46,9 +46,9 @@ pnpm add @brotu/ai
46
46
  ## Client
47
47
 
48
48
  ```ts
49
- import { brotuClient } from "@brotu/ai";
49
+ import { brotu } from "@brotu/ai";
50
50
 
51
- const ai = brotuClient({
51
+ const ai = brotu({
52
52
  apiKey: process.env.BROTU_API_KEY!, // brotu_sk_… from https://brotu.app
53
53
  providers: {
54
54
  kling: { apiKey: process.env.KLING_API_KEY! },
@@ -71,18 +71,36 @@ Every public call returns `{ data, error }`. `data` is unusable until you narrow
71
71
 
72
72
  | Surface | Methods |
73
73
  |---|---|
74
- | `ai.video` | `submit` / `generate` |
75
- | `ai.image` | `submit` / `generate` |
76
- | `ai.text` | `submit` / `generate` |
77
- | `ai.audio` | `submit` / `generate` |
74
+ | `ai.video` | `submit` / `generate` / `list` |
75
+ | `ai.image` | `submit` / `generate` / `list` |
76
+ | `ai.text` | `submit` / `generate` / `list` |
77
+ | `ai.audio` | `submit` / `generate` / `list` |
78
78
  | `ai.jobs` | `poll` / `wait` |
79
79
  | `ai.webhook` | `set` / `clear` / `get` |
80
+ | `hooks` | constructor option: your own code on start, success and failure |
80
81
  | `ai.estimateCost` | units, and USD when verified |
81
82
  | `ai.kling` | `motionControl`, `omniVideo`, `avatar`, `outpainting`, `imageOmni` |
82
83
  | `ai.google` | `omniVideo` (conversational refine) |
83
84
 
84
85
  `ai.kling` and `ai.google` exist only when that key is set.
85
86
 
87
+ ## What your keys reach
88
+
89
+ The platform generates image and video. Speech and text are the vendor's work, so those need that vendor's key. `list` says which is which, per model:
90
+
91
+ ```ts
92
+ const ai = brotu({ apiKey: process.env.BROTU_API_KEY! });
93
+
94
+ ai.image.list();
95
+ // [{ model: { id: "gpt-image-2", … }, runsOn: "brotu", runnable: true }, …]
96
+
97
+ ai.audio.list();
98
+ // [{ model: { id: "kling/tts", … }, runsOn: "kling", runnable: false,
99
+ // reason: "Brotu generates image and video only. Pass providers.kling to run this one." }, …]
100
+ ```
101
+
102
+ With a Brotu key alone that is 29 image and 37 video models, and the 31 speech and text models listed with the key they are waiting on. Models you cannot reach stay in the list on purpose: hiding them would hide the gap. `ai.models()` is the filtered view, only what runs.
103
+
86
104
  ## Video
87
105
 
88
106
  `submit` returns a job handle. `generate` waits. Video takes minutes, so do not `generate` inside a request handler.
@@ -205,6 +223,26 @@ The POST is JSON. Check `x-brotu-event` and, if you set a secret, `x-brotu-webho
205
223
 
206
224
  Error codes: `unknown_model`, `missing_key`, `unsupported_provider`, `invalid_request`, `provider_error`, `timeout`.
207
225
 
226
+ ## Hooks
227
+
228
+ Same moments as the webhook, but a function instead of an endpoint — so it runs in your process, with your imports.
229
+
230
+ ```ts
231
+ const ai = brotu({
232
+ apiKey: process.env.BROTU_API_KEY!,
233
+ providers: { kling: { apiKey: process.env.KLING_API_KEY! } },
234
+ hooks: {
235
+ onVideoLoading: (e) => console.log("generating", e.model),
236
+ onVideoSuccess: (e) => sendEmail("your video is ready", e.outputs?.[0]?.url),
237
+ onVideoError: (e) => sendEmail("your video failed", e.error?.message),
238
+ },
239
+ });
240
+ ```
241
+
242
+ One optional callback per kind and stage — `on{Image,Video,Audio,Text}{Loading,Success,Error}`, twelve typed names. `Loading` fires once the model is routed, before the provider is called; `Success` and `Error` fire wherever the webhook fires, deduped by job id. A hook that throws never fails the generation.
243
+
244
+ The event is `HookEvent`: `kind`, `stage`, `provider`, `model`, `jobId`, `outputs`, `error`, `metadata`, `processingTimeMs`, `at`.
245
+
208
246
  ## Motion control, avatars, omni
209
247
 
210
248
  Not on `ai.video`. A shared signature would be a lie.
@@ -1 +1 @@
1
- {"version":3,"file":"brotu.adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/brotu.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,GAAG,EACR,KAAK,WAAW,EAEhB,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,YAAY,EAEjB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,MAAM,iCAAiC,CAAC;AAGzC,MAAM,WAAW,mBAAmB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAMD;;;GAGG;AACH,QAAA,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAO9C,CAAC;AAuBF,qBAAa,YAAa,YAAW,oBAAoB;IAM5C,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,QAAQ,CAAC,YAAY,WAAW;IAChC,QAAQ,CAAC,cAAc,EAAE,cAAc,EAAE,CAAsB;IAE/D,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAElC,YAA6B,IAAI,EAAE,mBAAmB,EAAI;IAE1D,OAAO,KAAK,MAAM,GAEjB;IAED,OAAO,CAAC,OAAO;YAOD,WAAW;IAiBzB,OAAO,CAAC,UAAU;YAKJ,OAAO;IAyBrB,OAAO,CAAC,aAAa;YAIP,eAAe;IAwB7B,OAAO,CAAC,SAAS;IAgBjB,OAAO,CAAC,SAAS;YAqBH,GAAG;IA4DX,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CAgChD;IAED,OAAO,CAAC,iBAAiB;IAUzB,aAAa,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAEtE;IAED,aAAa,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAEtE;IAEK,YAAY,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAU1E;IAEK,aAAa,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAU5E;IAEK,YAAY,CACjB,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,gBAAgB,GACtB,OAAO,CAAC,YAAY,CAAC,CAuDvB;IAED,aAAa,IAAI,OAAO,CAEvB;IAED,kBAAkB,IAAI;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,cAAc,CAAA;KAAE,EAAE,CAEzE;CACD;AA2BD,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
1
+ {"version":3,"file":"brotu.adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/brotu.adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAEN,KAAK,GAAG,EACR,KAAK,WAAW,EAEhB,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,YAAY,EAEjB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,MAAM,iCAAiC,CAAC;AAGzC,MAAM,WAAW,mBAAmB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAMD;;;GAGG;AACH,QAAA,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAO9C,CAAC;AAuBF,qBAAa,YAAa,YAAW,oBAAoB;IAM5C,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,QAAQ,CAAC,YAAY,WAAW;IAChC,QAAQ,CAAC,cAAc,EAAE,cAAc,EAAE,CAA8B;IAEvE,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAElC,YAA6B,IAAI,EAAE,mBAAmB,EAAI;IAE1D,OAAO,KAAK,MAAM,GAEjB;IAED,OAAO,CAAC,OAAO;YAOD,WAAW;IAiBzB,OAAO,CAAC,UAAU;YAKJ,OAAO;IAyBrB,OAAO,CAAC,aAAa;YAIP,eAAe;IAwB7B,OAAO,CAAC,SAAS;IAgBjB,OAAO,CAAC,SAAS;YAqBH,GAAG;IA4DX,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CAgChD;IAED,OAAO,CAAC,iBAAiB;IAUzB,aAAa,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAEtE;IAED,aAAa,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAEtE;IAEK,YAAY,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAU1E;IAEK,aAAa,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAU5E;IAEK,YAAY,CACjB,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,gBAAgB,GACtB,OAAO,CAAC,YAAY,CAAC,CAuDvB;IAED,aAAa,IAAI,OAAO,CAEvB;IAED,kBAAkB,IAAI;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,cAAc,CAAA;KAAE,EAAE,CAEzE;CACD;AA2BD,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
package/dist/catalog.cjs CHANGED
@@ -20,7 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/catalog.ts
21
21
  var catalog_exports = {};
22
22
  __export(catalog_exports, {
23
+ BROTU_SUPPORTED_CATEGORIES: () => BROTU_SUPPORTED_CATEGORIES,
23
24
  DEFAULT_BROTU_API_URL: () => DEFAULT_BROTU_API_URL,
25
+ describeModels: () => describeModels,
24
26
  getAvailableModels: () => getAvailableModels,
25
27
  getModel: () => getModel,
26
28
  getModels: () => getModels,
@@ -1084,18 +1086,50 @@ function resolveProvider(modelId, options) {
1084
1086
  `Model "${modelId}" runs on "${id}", but no API key was given for it (configured: ${owned}). Pass a Brotu key to generate on the platform.`
1085
1087
  );
1086
1088
  }
1087
- function getAvailableModels(options) {
1088
- if (brotuApiKey(options)) {
1089
- return catalog.filter((model) => Boolean(model.provider));
1090
- }
1089
+ var BROTU_SUPPORTED_CATEGORIES = ["image", "video"];
1090
+ function describeModels(options) {
1091
1091
  const vendors = vendorProviders(options);
1092
- return catalog.filter(
1093
- (model) => model.provider && model.provider in vendors
1094
- );
1092
+ const platformKey = brotuApiKey(options);
1093
+ return catalog.map((model) => {
1094
+ const provider = model.provider;
1095
+ if (!provider) {
1096
+ return {
1097
+ model,
1098
+ runsOn: "(none)",
1099
+ runnable: false,
1100
+ reason: "The model declares no provider."
1101
+ };
1102
+ }
1103
+ if (provider in vendors) {
1104
+ return { model, runsOn: provider, runnable: true };
1105
+ }
1106
+ if (platformKey) {
1107
+ if (BROTU_SUPPORTED_CATEGORIES.includes(model.category)) {
1108
+ return { model, runsOn: "brotu", runnable: true };
1109
+ }
1110
+ return {
1111
+ model,
1112
+ runsOn: provider,
1113
+ runnable: false,
1114
+ reason: `Brotu generates ${BROTU_SUPPORTED_CATEGORIES.join(" and ")} only. Pass providers.${provider} to run this one.`
1115
+ };
1116
+ }
1117
+ return {
1118
+ model,
1119
+ runsOn: provider,
1120
+ runnable: false,
1121
+ reason: `No key for "${provider}". Pass a Brotu key or providers.${provider}.`
1122
+ };
1123
+ });
1124
+ }
1125
+ function getAvailableModels(options) {
1126
+ return describeModels(options).filter((entry) => entry.runnable).map((entry) => entry.model);
1095
1127
  }
1096
1128
  // Annotate the CommonJS export names for ESM import in node:
1097
1129
  0 && (module.exports = {
1130
+ BROTU_SUPPORTED_CATEGORIES,
1098
1131
  DEFAULT_BROTU_API_URL,
1132
+ describeModels,
1099
1133
  getAvailableModels,
1100
1134
  getModel,
1101
1135
  getModels,
package/dist/catalog.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AIModelConfig } from "./constants/model.types";
1
+ import type { AIModelCategory, AIModelConfig } from "./constants/model.types";
2
2
  import type { BrotuAIOptions, ResolvedProvider } from "./types";
3
3
  export declare const DEFAULT_BROTU_API_URL: string;
4
4
  /**
@@ -23,6 +23,27 @@ export declare function getProviders(): string[];
23
23
  * aggregator, so there is no wallet to guess between.
24
24
  */
25
25
  export declare function resolveProvider(modelId: string, options: BrotuAIOptions): ResolvedProvider;
26
+ /**
27
+ * What the platform generates on credits. Speech and text are the vendor's
28
+ * work, so a Brotu key alone does not reach them. The Brotu adapter reads this
29
+ * too, so the catalog and the adapter cannot drift apart.
30
+ */
31
+ export declare const BROTU_SUPPORTED_CATEGORIES: AIModelCategory[];
32
+ /** A model plus where it would actually run with the keys at hand. */
33
+ export interface ModelAvailability {
34
+ model: AIModelConfig;
35
+ /** Provider that serves it: a vendor id, or "brotu" for the platform. */
36
+ runsOn: string;
37
+ /** False when nothing configured can run it. `reason` says why. */
38
+ runnable: boolean;
39
+ reason?: string;
40
+ }
41
+ /**
42
+ * Every catalog model, each labelled with the host that would serve it. This is
43
+ * the honest view: `runnable: false` entries are the ones a key is missing for,
44
+ * and they stay in the list precisely so the caller can see what is missing.
45
+ */
46
+ export declare function describeModels(options: BrotuAIOptions): ModelAvailability[];
26
47
  /** Models the caller can actually run, given the keys they supplied. */
27
48
  export declare function getAvailableModels(options: BrotuAIOptions): AIModelConfig[];
28
49
  //# sourceMappingURL=catalog.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAU7D,OAAO,KAAK,EAAE,cAAc,EAAkB,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAgBhF,eAAO,MAAM,qBAAqB,QAA2B,CAAC;AA+B9D;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,GAAE,aAAa,EAAO,GAAG,IAAI,CAUjE;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,IAAI,IAAI,CAGnC;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAEnE;AAED,wBAAgB,SAAS,IAAI,aAAa,EAAE,CAE3C;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,wDAAwD;AACxD,wBAAgB,YAAY,IAAI,MAAM,EAAE,CAQvC;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,cAAc,GACrB,gBAAgB,CAqClB;AAED,wEAAwE;AACxE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,EAAE,CAQ3E"}
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAU9E,OAAO,KAAK,EAAE,cAAc,EAAkB,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAgBhF,eAAO,MAAM,qBAAqB,QAA2B,CAAC;AA+B9D;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,GAAE,aAAa,EAAO,GAAG,IAAI,CAUjE;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,IAAI,IAAI,CAGnC;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAEnE;AAED,wBAAgB,SAAS,IAAI,aAAa,EAAE,CAE3C;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,wDAAwD;AACxD,wBAAgB,YAAY,IAAI,MAAM,EAAE,CAQvC;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,cAAc,GACrB,gBAAgB,CAqClB;AAED;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,EAAE,eAAe,EAAuB,CAAC;AAEhF,sEAAsE;AACtE,MAAM,WAAW,iBAAiB;IACjC,KAAK,EAAE,aAAa,CAAC;IACrB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,iBAAiB,EAAE,CAmC3E;AAED,wEAAwE;AACxE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,EAAE,CAI3E"}
package/dist/catalog.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import {
2
+ BROTU_SUPPORTED_CATEGORIES,
2
3
  DEFAULT_BROTU_API_URL,
4
+ describeModels,
3
5
  getAvailableModels,
4
6
  getModel,
5
7
  getModels,
@@ -8,9 +10,11 @@ import {
8
10
  registerModels,
9
11
  resetCatalog,
10
12
  resolveProvider
11
- } from "./chunk-QOWHHKSI.js";
13
+ } from "./chunk-3MC3RDAJ.js";
12
14
  export {
15
+ BROTU_SUPPORTED_CATEGORIES,
13
16
  DEFAULT_BROTU_API_URL,
17
+ describeModels,
14
18
  getAvailableModels,
15
19
  getModel,
16
20
  getModels,
@@ -1247,14 +1247,44 @@ function resolveProvider(modelId, options) {
1247
1247
  `Model "${modelId}" runs on "${id}", but no API key was given for it (configured: ${owned}). Pass a Brotu key to generate on the platform.`
1248
1248
  );
1249
1249
  }
1250
- function getAvailableModels(options) {
1251
- if (brotuApiKey(options)) {
1252
- return catalog.filter((model) => Boolean(model.provider));
1253
- }
1250
+ var BROTU_SUPPORTED_CATEGORIES = ["image", "video"];
1251
+ function describeModels(options) {
1254
1252
  const vendors = vendorProviders(options);
1255
- return catalog.filter(
1256
- (model) => model.provider && model.provider in vendors
1257
- );
1253
+ const platformKey = brotuApiKey(options);
1254
+ return catalog.map((model) => {
1255
+ const provider = model.provider;
1256
+ if (!provider) {
1257
+ return {
1258
+ model,
1259
+ runsOn: "(none)",
1260
+ runnable: false,
1261
+ reason: "The model declares no provider."
1262
+ };
1263
+ }
1264
+ if (provider in vendors) {
1265
+ return { model, runsOn: provider, runnable: true };
1266
+ }
1267
+ if (platformKey) {
1268
+ if (BROTU_SUPPORTED_CATEGORIES.includes(model.category)) {
1269
+ return { model, runsOn: "brotu", runnable: true };
1270
+ }
1271
+ return {
1272
+ model,
1273
+ runsOn: provider,
1274
+ runnable: false,
1275
+ reason: `Brotu generates ${BROTU_SUPPORTED_CATEGORIES.join(" and ")} only. Pass providers.${provider} to run this one.`
1276
+ };
1277
+ }
1278
+ return {
1279
+ model,
1280
+ runsOn: provider,
1281
+ runnable: false,
1282
+ reason: `No key for "${provider}". Pass a Brotu key or providers.${provider}.`
1283
+ };
1284
+ });
1285
+ }
1286
+ function getAvailableModels(options) {
1287
+ return describeModels(options).filter((entry) => entry.runnable).map((entry) => entry.model);
1258
1288
  }
1259
1289
 
1260
1290
  export {
@@ -1294,5 +1324,7 @@ export {
1294
1324
  hasModel,
1295
1325
  getProviders,
1296
1326
  resolveProvider,
1327
+ BROTU_SUPPORTED_CATEGORIES,
1328
+ describeModels,
1297
1329
  getAvailableModels
1298
1330
  };
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { getModel } from "./catalog";
1
+ import { getModel, type ModelAvailability } from "./catalog";
2
2
  import type { AIModelConfig } from "./constants/model.types";
3
3
  import { type AIError, type Generation, type Result } from "./helpers/result";
4
4
  import { type Job, type JobSnapshot } from "./lib/jobs";
@@ -14,19 +14,30 @@ export interface BrotuAI {
14
14
  submit(params: ImageGenerationParams): Promise<Result<Job>>;
15
15
  /** submit + wait. Convenient, but holds the call open for the whole run. */
16
16
  generate(params: ImageGenerationParams): Promise<Result<Generation>>;
17
+ /**
18
+ * Every image model, each labelled with the host that would serve it
19
+ * (`runsOn`) and whether your keys reach it (`runnable`). Unreachable
20
+ * models stay in the list, with `reason`, so the gap is visible.
21
+ */
22
+ list(): ModelAvailability[];
17
23
  };
18
24
  video: {
19
25
  submit(params: VideoGenerationParams): Promise<Result<Job>>;
20
26
  generate(params: VideoGenerationParams): Promise<Result<Generation>>;
27
+ list(): ModelAvailability[];
21
28
  };
22
29
  text: {
23
30
  submit(params: TextGenerationParams): Promise<Result<Job>>;
24
31
  generate(params: TextGenerationParams): Promise<Result<Generation>>;
32
+ /** Text runs on the vendor, so these need `providers`, not a Brotu key. */
33
+ list(): ModelAvailability[];
25
34
  };
26
35
  /** Speech synthesis. `prompt` is the text to speak. */
27
36
  audio: {
28
37
  submit(params: AudioGenerationParams): Promise<Result<Job>>;
29
38
  generate(params: AudioGenerationParams): Promise<Result<Generation>>;
39
+ /** Speech runs on the vendor, so these need `providers`, not a Brotu key. */
40
+ list(): ModelAvailability[];
30
41
  };
31
42
  jobs: {
32
43
  /** Ask once. Returns straight away, settled or not. */
@@ -90,7 +101,7 @@ export interface BrotuAI {
90
101
  */
91
102
  estimateCost(type: GenerationType, params: GenerationParams): Promise<Result<CostEstimate>>;
92
103
  }
93
- export declare function brotuClient(options: BrotuAIOptions): BrotuAI;
104
+ export declare function brotu(options: BrotuAIOptions): BrotuAI;
94
105
  export type { AIError, Generation, Result };
95
106
  export { getModel };
96
107
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ BROTU_SUPPORTED_CATEGORIES: () => BROTU_SUPPORTED_CATEGORIES,
33
34
  BYTEPLUS_CATALOG: () => BYTEPLUS_CATALOG,
34
35
  BYTEPLUS_MODELS: () => BYTEPLUS_MODELS,
35
36
  BrotuAdapter: () => BrotuAdapter,
@@ -61,9 +62,10 @@ __export(index_exports, {
61
62
  QWEN_TEXT_MODELS: () => QWEN_TEXT_MODELS,
62
63
  QWEN_VIDEO_MODELS: () => QWEN_VIDEO_MODELS,
63
64
  QwenAdapter: () => QwenAdapter,
64
- brotuClient: () => brotuClient,
65
+ brotu: () => brotu,
65
66
  createS3Storage: () => createS3Storage,
66
67
  deliverWebhook: () => deliverWebhook,
68
+ describeModels: () => describeModels,
67
69
  fail: () => fail,
68
70
  getAvailableModels: () => getAvailableModels,
69
71
  getModel: () => getModel,
@@ -80,34 +82,6 @@ __export(index_exports, {
80
82
  });
81
83
  module.exports = __toCommonJS(index_exports);
82
84
 
83
- // src/lib/jobs.ts
84
- var import_node_async_hooks = require("async_hooks");
85
- var PendingJob = class extends Error {
86
- taskId;
87
- pollEndpoint;
88
- constructor(taskId, pollEndpoint) {
89
- super(`Job ${taskId} was submitted and is still running.`);
90
- this.name = "PendingJob";
91
- this.taskId = taskId;
92
- this.pollEndpoint = pollEndpoint;
93
- }
94
- };
95
- function isPendingJob(error) {
96
- return error instanceof PendingJob;
97
- }
98
- var submitMode = new import_node_async_hooks.AsyncLocalStorage();
99
- function runInSubmitMode(fn) {
100
- return submitMode.run(true, fn);
101
- }
102
- function isSubmitMode() {
103
- return submitMode.getStore() === true;
104
- }
105
-
106
- // src/ports/content-generator.port.ts
107
- function expiresInHours(hours) {
108
- return new Date(Date.now() + hours * 36e5).toISOString();
109
- }
110
-
111
85
  // src/providers/byteplus.models.ts
112
86
  var FPS = 24;
113
87
  var PIXELS = {
@@ -1357,14 +1331,72 @@ function resolveProvider(modelId, options) {
1357
1331
  `Model "${modelId}" runs on "${id}", but no API key was given for it (configured: ${owned}). Pass a Brotu key to generate on the platform.`
1358
1332
  );
1359
1333
  }
1334
+ var BROTU_SUPPORTED_CATEGORIES = ["image", "video"];
1335
+ function describeModels(options) {
1336
+ const vendors = vendorProviders(options);
1337
+ const platformKey = brotuApiKey(options);
1338
+ return catalog.map((model) => {
1339
+ const provider = model.provider;
1340
+ if (!provider) {
1341
+ return {
1342
+ model,
1343
+ runsOn: "(none)",
1344
+ runnable: false,
1345
+ reason: "The model declares no provider."
1346
+ };
1347
+ }
1348
+ if (provider in vendors) {
1349
+ return { model, runsOn: provider, runnable: true };
1350
+ }
1351
+ if (platformKey) {
1352
+ if (BROTU_SUPPORTED_CATEGORIES.includes(model.category)) {
1353
+ return { model, runsOn: "brotu", runnable: true };
1354
+ }
1355
+ return {
1356
+ model,
1357
+ runsOn: provider,
1358
+ runnable: false,
1359
+ reason: `Brotu generates ${BROTU_SUPPORTED_CATEGORIES.join(" and ")} only. Pass providers.${provider} to run this one.`
1360
+ };
1361
+ }
1362
+ return {
1363
+ model,
1364
+ runsOn: provider,
1365
+ runnable: false,
1366
+ reason: `No key for "${provider}". Pass a Brotu key or providers.${provider}.`
1367
+ };
1368
+ });
1369
+ }
1360
1370
  function getAvailableModels(options) {
1361
- if (brotuApiKey(options)) {
1362
- return catalog.filter((model) => Boolean(model.provider));
1371
+ return describeModels(options).filter((entry) => entry.runnable).map((entry) => entry.model);
1372
+ }
1373
+
1374
+ // src/lib/jobs.ts
1375
+ var import_node_async_hooks = require("async_hooks");
1376
+ var PendingJob = class extends Error {
1377
+ taskId;
1378
+ pollEndpoint;
1379
+ constructor(taskId, pollEndpoint) {
1380
+ super(`Job ${taskId} was submitted and is still running.`);
1381
+ this.name = "PendingJob";
1382
+ this.taskId = taskId;
1383
+ this.pollEndpoint = pollEndpoint;
1363
1384
  }
1364
- const vendors = vendorProviders(options);
1365
- return catalog.filter(
1366
- (model) => model.provider && model.provider in vendors
1367
- );
1385
+ };
1386
+ function isPendingJob(error) {
1387
+ return error instanceof PendingJob;
1388
+ }
1389
+ var submitMode = new import_node_async_hooks.AsyncLocalStorage();
1390
+ function runInSubmitMode(fn) {
1391
+ return submitMode.run(true, fn);
1392
+ }
1393
+ function isSubmitMode() {
1394
+ return submitMode.getStore() === true;
1395
+ }
1396
+
1397
+ // src/ports/content-generator.port.ts
1398
+ function expiresInHours(hours) {
1399
+ return new Date(Date.now() + hours * 36e5).toISOString();
1368
1400
  }
1369
1401
 
1370
1402
  // src/adapters/estimate.ts
@@ -1412,7 +1444,7 @@ var BrotuAdapter = class {
1412
1444
  }
1413
1445
  opts;
1414
1446
  providerName = "brotu";
1415
- supportedTypes = ["image", "video"];
1447
+ supportedTypes = BROTU_SUPPORTED_CATEGORIES;
1416
1448
  workspaceIdCache;
1417
1449
  get origin() {
1418
1450
  return (this.opts.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
@@ -3740,6 +3772,22 @@ function failFrom(code, cause, context) {
3740
3772
  });
3741
3773
  }
3742
3774
 
3775
+ // src/lib/hooks.ts
3776
+ function hookName(kind, stage) {
3777
+ return `on${kind[0].toUpperCase()}${kind.slice(1)}${stage}`;
3778
+ }
3779
+ function findHook(hooks, kind, stage) {
3780
+ if (!hooks || !kind) return void 0;
3781
+ return hooks[hookName(kind, stage)];
3782
+ }
3783
+ async function runHook(hook, event) {
3784
+ if (!hook) return;
3785
+ try {
3786
+ await hook(event);
3787
+ } catch {
3788
+ }
3789
+ }
3790
+
3743
3791
  // src/lib/storage.ts
3744
3792
  async function loadS3(config) {
3745
3793
  let s3;
@@ -3910,7 +3958,7 @@ var NATIVE_PROVIDERS = [
3910
3958
  "openai",
3911
3959
  "qwen"
3912
3960
  ];
3913
- function brotuClient(options) {
3961
+ function brotu(options) {
3914
3962
  const apiKey = options.apiKey?.trim();
3915
3963
  if (!apiKey) {
3916
3964
  throw new Error(
@@ -4023,7 +4071,10 @@ function brotuClient(options) {
4023
4071
  }
4024
4072
  async function notifySettled(input) {
4025
4073
  const config = webhookFor(input.params);
4026
- if (!config) return;
4074
+ const kind = input.kind ?? input.job?.kind;
4075
+ const stage = input.event === "generation.succeeded" ? "Success" : "Error";
4076
+ const hook = findHook(options.hooks, kind, stage);
4077
+ if (!config && !hook) return;
4027
4078
  const jobId = input.job?.id;
4028
4079
  if (jobId) {
4029
4080
  if (notifiedJobIds.has(jobId)) return;
@@ -4034,14 +4085,38 @@ function brotuClient(options) {
4034
4085
  jobId,
4035
4086
  provider: input.provider ?? input.job?.provider,
4036
4087
  model: input.model ?? input.job?.model,
4037
- kind: input.kind ?? input.job?.kind,
4088
+ kind,
4038
4089
  outputs: input.outputs,
4039
4090
  error: input.error ? { code: input.error.code, message: input.error.message } : void 0,
4040
4091
  metadata: input.metadata ?? input.job?.metadata,
4041
4092
  processingTimeMs: input.processingTimeMs,
4042
4093
  completedAt: (/* @__PURE__ */ new Date()).toISOString()
4043
4094
  };
4044
- await deliverWebhook(config, payload);
4095
+ if (hook && kind) {
4096
+ await runHook(hook, {
4097
+ kind,
4098
+ stage,
4099
+ provider: payload.provider ?? "",
4100
+ model: payload.model ?? "",
4101
+ jobId: payload.jobId,
4102
+ outputs: payload.outputs,
4103
+ error: payload.error,
4104
+ metadata: payload.metadata,
4105
+ processingTimeMs: payload.processingTimeMs,
4106
+ at: payload.completedAt
4107
+ });
4108
+ }
4109
+ if (config) await deliverWebhook(config, payload);
4110
+ }
4111
+ function notifyLoading(kind, routed, params) {
4112
+ return runHook(findHook(options.hooks, kind, "Loading"), {
4113
+ kind,
4114
+ stage: "Loading",
4115
+ provider: routed.provider,
4116
+ model: routed.model,
4117
+ metadata: params.metadata,
4118
+ at: (/* @__PURE__ */ new Date()).toISOString()
4119
+ });
4045
4120
  }
4046
4121
  async function toGeneration(raw, metadata) {
4047
4122
  if (!raw.success) {
@@ -4064,6 +4139,7 @@ function brotuClient(options) {
4064
4139
  async function generate(kind, params) {
4065
4140
  const routed = route(params.model);
4066
4141
  if (routed.error) return fail(routed.error);
4142
+ await notifyLoading(kind, routed.data, params);
4067
4143
  try {
4068
4144
  const result = await toGeneration(
4069
4145
  await generateWith(routed.data.adapter, kind, params),
@@ -4114,6 +4190,7 @@ function brotuClient(options) {
4114
4190
  async function submit(kind, params) {
4115
4191
  const routed = route(params.model);
4116
4192
  if (routed.error) return fail(routed.error);
4193
+ await notifyLoading(kind, routed.data, params);
4117
4194
  const base = {
4118
4195
  provider: routed.data.provider,
4119
4196
  model: routed.data.model,
@@ -4285,24 +4362,33 @@ function brotuClient(options) {
4285
4362
  }
4286
4363
  };
4287
4364
  }
4365
+ function listFor(category) {
4366
+ return describeModels(optionsWithKey).filter(
4367
+ (entry) => entry.model.category === category
4368
+ );
4369
+ }
4288
4370
  return {
4289
4371
  google: googleNamespace(),
4290
4372
  kling: klingNamespace(),
4291
4373
  image: {
4292
4374
  submit: (params) => submit("image", params),
4293
- generate: (params) => generate("image", params)
4375
+ generate: (params) => generate("image", params),
4376
+ list: () => listFor("image")
4294
4377
  },
4295
4378
  video: {
4296
4379
  submit: (params) => submit("video", params),
4297
- generate: (params) => generate("video", params)
4380
+ generate: (params) => generate("video", params),
4381
+ list: () => listFor("video")
4298
4382
  },
4299
4383
  text: {
4300
4384
  submit: (params) => submit("text", params),
4301
- generate: (params) => generate("text", params)
4385
+ generate: (params) => generate("text", params),
4386
+ list: () => listFor("text")
4302
4387
  },
4303
4388
  audio: {
4304
4389
  submit: (params) => submit("audio", params),
4305
- generate: (params) => generate("audio", params)
4390
+ generate: (params) => generate("audio", params),
4391
+ list: () => listFor("audio")
4306
4392
  },
4307
4393
  webhook: {
4308
4394
  set(value) {
@@ -4369,6 +4455,7 @@ function brotuClient(options) {
4369
4455
  }
4370
4456
  // Annotate the CommonJS export names for ESM import in node:
4371
4457
  0 && (module.exports = {
4458
+ BROTU_SUPPORTED_CATEGORIES,
4372
4459
  BYTEPLUS_CATALOG,
4373
4460
  BYTEPLUS_MODELS,
4374
4461
  BrotuAdapter,
@@ -4400,9 +4487,10 @@ function brotuClient(options) {
4400
4487
  QWEN_TEXT_MODELS,
4401
4488
  QWEN_VIDEO_MODELS,
4402
4489
  QwenAdapter,
4403
- brotuClient,
4490
+ brotu,
4404
4491
  createS3Storage,
4405
4492
  deliverWebhook,
4493
+ describeModels,
4406
4494
  fail,
4407
4495
  getAvailableModels,
4408
4496
  getModel,
package/dist/index.d.ts CHANGED
@@ -12,11 +12,13 @@ export type { OpenAIAdapterOptions } from "./adapters/openai.adapter";
12
12
  export { OpenAIAdapter } from "./adapters/openai.adapter";
13
13
  export type { QwenAdapterOptions } from "./adapters/qwen.adapter";
14
14
  export { QwenAdapter } from "./adapters/qwen.adapter";
15
- export { DEFAULT_BROTU_API_URL, getAvailableModels, getModel, getModels, getProviders, hasModel, registerModels, resetCatalog, resolveProvider, } from "./catalog";
15
+ export type { ModelAvailability } from "./catalog";
16
+ export { BROTU_SUPPORTED_CATEGORIES, DEFAULT_BROTU_API_URL, describeModels, getAvailableModels, getModel, getModels, getProviders, hasModel, registerModels, resetCatalog, resolveProvider, } from "./catalog";
16
17
  export type { BrotuAI } from "./client";
17
- export { brotuClient } from "./client";
18
+ export { brotu } from "./client";
18
19
  export type * from "./constants/model.types";
19
20
  export { type AIError, type AIErrorCode, fail, type Generation, ok, type Result, } from "./helpers/result";
21
+ export type { HookEvent, HookFn, Hooks, HookStage, } from "./lib/hooks";
20
22
  export { isPendingJob, type Job, type JobSnapshot, type JobStatus, PendingJob, } from "./lib/jobs";
21
23
  export { createS3Storage, persistOutputs, type S3StorageConfig, type Storage, } from "./lib/storage";
22
24
  export { deliverWebhook, resolveWebhook, type WebhookConfig, type WebhookEvent, type WebhookEventName, } from "./lib/webhook";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ BROTU_SUPPORTED_CATEGORIES,
2
3
  BYTEPLUS_CATALOG,
3
4
  BYTEPLUS_IMAGE_MODELS,
4
5
  BYTEPLUS_MODELS,
@@ -26,6 +27,7 @@ import {
26
27
  QWEN_IMAGE_PATHS,
27
28
  QWEN_TEXT_MODELS,
28
29
  QWEN_VIDEO_MODELS,
30
+ describeModels,
29
31
  fieldsFor,
30
32
  getAvailableModels,
31
33
  getModel,
@@ -36,7 +38,7 @@ import {
36
38
  resetCatalog,
37
39
  resolveProvider,
38
40
  videoPathFor
39
- } from "./chunk-QOWHHKSI.js";
41
+ } from "./chunk-3MC3RDAJ.js";
40
42
 
41
43
  // src/lib/jobs.ts
42
44
  import { AsyncLocalStorage } from "async_hooks";
@@ -111,7 +113,7 @@ var BrotuAdapter = class {
111
113
  }
112
114
  opts;
113
115
  providerName = "brotu";
114
- supportedTypes = ["image", "video"];
116
+ supportedTypes = BROTU_SUPPORTED_CATEGORIES;
115
117
  workspaceIdCache;
116
118
  get origin() {
117
119
  return (this.opts.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
@@ -2439,6 +2441,22 @@ function failFrom(code, cause, context) {
2439
2441
  });
2440
2442
  }
2441
2443
 
2444
+ // src/lib/hooks.ts
2445
+ function hookName(kind, stage) {
2446
+ return `on${kind[0].toUpperCase()}${kind.slice(1)}${stage}`;
2447
+ }
2448
+ function findHook(hooks, kind, stage) {
2449
+ if (!hooks || !kind) return void 0;
2450
+ return hooks[hookName(kind, stage)];
2451
+ }
2452
+ async function runHook(hook, event) {
2453
+ if (!hook) return;
2454
+ try {
2455
+ await hook(event);
2456
+ } catch {
2457
+ }
2458
+ }
2459
+
2442
2460
  // src/lib/storage.ts
2443
2461
  async function loadS3(config) {
2444
2462
  let s3;
@@ -2609,7 +2627,7 @@ var NATIVE_PROVIDERS = [
2609
2627
  "openai",
2610
2628
  "qwen"
2611
2629
  ];
2612
- function brotuClient(options) {
2630
+ function brotu(options) {
2613
2631
  const apiKey = options.apiKey?.trim();
2614
2632
  if (!apiKey) {
2615
2633
  throw new Error(
@@ -2722,7 +2740,10 @@ function brotuClient(options) {
2722
2740
  }
2723
2741
  async function notifySettled(input) {
2724
2742
  const config = webhookFor(input.params);
2725
- if (!config) return;
2743
+ const kind = input.kind ?? input.job?.kind;
2744
+ const stage = input.event === "generation.succeeded" ? "Success" : "Error";
2745
+ const hook = findHook(options.hooks, kind, stage);
2746
+ if (!config && !hook) return;
2726
2747
  const jobId = input.job?.id;
2727
2748
  if (jobId) {
2728
2749
  if (notifiedJobIds.has(jobId)) return;
@@ -2733,14 +2754,38 @@ function brotuClient(options) {
2733
2754
  jobId,
2734
2755
  provider: input.provider ?? input.job?.provider,
2735
2756
  model: input.model ?? input.job?.model,
2736
- kind: input.kind ?? input.job?.kind,
2757
+ kind,
2737
2758
  outputs: input.outputs,
2738
2759
  error: input.error ? { code: input.error.code, message: input.error.message } : void 0,
2739
2760
  metadata: input.metadata ?? input.job?.metadata,
2740
2761
  processingTimeMs: input.processingTimeMs,
2741
2762
  completedAt: (/* @__PURE__ */ new Date()).toISOString()
2742
2763
  };
2743
- await deliverWebhook(config, payload);
2764
+ if (hook && kind) {
2765
+ await runHook(hook, {
2766
+ kind,
2767
+ stage,
2768
+ provider: payload.provider ?? "",
2769
+ model: payload.model ?? "",
2770
+ jobId: payload.jobId,
2771
+ outputs: payload.outputs,
2772
+ error: payload.error,
2773
+ metadata: payload.metadata,
2774
+ processingTimeMs: payload.processingTimeMs,
2775
+ at: payload.completedAt
2776
+ });
2777
+ }
2778
+ if (config) await deliverWebhook(config, payload);
2779
+ }
2780
+ function notifyLoading(kind, routed, params) {
2781
+ return runHook(findHook(options.hooks, kind, "Loading"), {
2782
+ kind,
2783
+ stage: "Loading",
2784
+ provider: routed.provider,
2785
+ model: routed.model,
2786
+ metadata: params.metadata,
2787
+ at: (/* @__PURE__ */ new Date()).toISOString()
2788
+ });
2744
2789
  }
2745
2790
  async function toGeneration(raw, metadata) {
2746
2791
  if (!raw.success) {
@@ -2763,6 +2808,7 @@ function brotuClient(options) {
2763
2808
  async function generate(kind, params) {
2764
2809
  const routed = route(params.model);
2765
2810
  if (routed.error) return fail(routed.error);
2811
+ await notifyLoading(kind, routed.data, params);
2766
2812
  try {
2767
2813
  const result = await toGeneration(
2768
2814
  await generateWith(routed.data.adapter, kind, params),
@@ -2813,6 +2859,7 @@ function brotuClient(options) {
2813
2859
  async function submit(kind, params) {
2814
2860
  const routed = route(params.model);
2815
2861
  if (routed.error) return fail(routed.error);
2862
+ await notifyLoading(kind, routed.data, params);
2816
2863
  const base = {
2817
2864
  provider: routed.data.provider,
2818
2865
  model: routed.data.model,
@@ -2984,24 +3031,33 @@ function brotuClient(options) {
2984
3031
  }
2985
3032
  };
2986
3033
  }
3034
+ function listFor(category) {
3035
+ return describeModels(optionsWithKey).filter(
3036
+ (entry) => entry.model.category === category
3037
+ );
3038
+ }
2987
3039
  return {
2988
3040
  google: googleNamespace(),
2989
3041
  kling: klingNamespace(),
2990
3042
  image: {
2991
3043
  submit: (params) => submit("image", params),
2992
- generate: (params) => generate("image", params)
3044
+ generate: (params) => generate("image", params),
3045
+ list: () => listFor("image")
2993
3046
  },
2994
3047
  video: {
2995
3048
  submit: (params) => submit("video", params),
2996
- generate: (params) => generate("video", params)
3049
+ generate: (params) => generate("video", params),
3050
+ list: () => listFor("video")
2997
3051
  },
2998
3052
  text: {
2999
3053
  submit: (params) => submit("text", params),
3000
- generate: (params) => generate("text", params)
3054
+ generate: (params) => generate("text", params),
3055
+ list: () => listFor("text")
3001
3056
  },
3002
3057
  audio: {
3003
3058
  submit: (params) => submit("audio", params),
3004
- generate: (params) => generate("audio", params)
3059
+ generate: (params) => generate("audio", params),
3060
+ list: () => listFor("audio")
3005
3061
  },
3006
3062
  webhook: {
3007
3063
  set(value) {
@@ -3067,6 +3123,7 @@ function brotuClient(options) {
3067
3123
  };
3068
3124
  }
3069
3125
  export {
3126
+ BROTU_SUPPORTED_CATEGORIES,
3070
3127
  BYTEPLUS_CATALOG,
3071
3128
  BYTEPLUS_MODELS,
3072
3129
  BrotuAdapter,
@@ -3098,9 +3155,10 @@ export {
3098
3155
  QWEN_TEXT_MODELS,
3099
3156
  QWEN_VIDEO_MODELS,
3100
3157
  QwenAdapter,
3101
- brotuClient,
3158
+ brotu,
3102
3159
  createS3Storage,
3103
3160
  deliverWebhook,
3161
+ describeModels,
3104
3162
  fail,
3105
3163
  getAvailableModels,
3106
3164
  getModel,
@@ -0,0 +1,32 @@
1
+ import type { AIError } from "../helpers/result";
2
+ import type { GenerationOutput, GenerationType } from "../ports/content-generator.port";
3
+ /** Where a generation is when the hook fires. */
4
+ export type HookStage = "Loading" | "Success" | "Error";
5
+ /** What every hook receives. Fields absent at that stage stay undefined. */
6
+ export interface HookEvent {
7
+ kind: GenerationType;
8
+ stage: HookStage;
9
+ provider: string;
10
+ model: string;
11
+ jobId?: string;
12
+ outputs?: GenerationOutput[];
13
+ error?: Pick<AIError, "code" | "message">;
14
+ metadata?: Record<string, string>;
15
+ processingTimeMs?: number;
16
+ at: string;
17
+ }
18
+ export type HookFn = (event: HookEvent) => void | Promise<void>;
19
+ /**
20
+ * One optional callback per kind and stage — `onVideoLoading`,
21
+ * `onImageSuccess`, `onAudioError`, and so on for every combination.
22
+ */
23
+ export type Hooks = Partial<Record<`on${Capitalize<GenerationType>}${HookStage}`, HookFn>>;
24
+ /** The hook registered for this kind and stage, if any. */
25
+ export declare function findHook(hooks: Hooks | undefined, kind: GenerationType | undefined, stage: HookStage): HookFn | undefined;
26
+ /**
27
+ * Run a hook without letting it break the caller. Same contract as the
28
+ * webhook: your logging or your mailer going down must not fail a generation
29
+ * that already succeeded.
30
+ */
31
+ export declare function runHook(hook: HookFn | undefined, event: HookEvent): Promise<void>;
32
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/lib/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EACX,gBAAgB,EAChB,cAAc,EACd,MAAM,iCAAiC,CAAC;AAEzC,iDAAiD;AACjD,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAExD,4EAA4E;AAC5E,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC7B,KAAK,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,EAAE,EAAE,MAAM,CAAC;CACX;AAED,MAAM,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEhE;;;GAGG;AACH,MAAM,MAAM,KAAK,GAAG,OAAO,CAC1B,MAAM,CAAC,KAAK,UAAU,CAAC,cAAc,CAAC,GAAG,SAAS,EAAE,EAAE,MAAM,CAAC,CAC7D,CAAC;AAMF,2DAA2D;AAC3D,wBAAgB,QAAQ,CACvB,KAAK,EAAE,KAAK,GAAG,SAAS,EACxB,IAAI,EAAE,cAAc,GAAG,SAAS,EAChC,KAAK,EAAE,SAAS,GACd,MAAM,GAAG,SAAS,CAGpB;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAC5B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,KAAK,EAAE,SAAS,GACd,OAAO,CAAC,IAAI,CAAC,CAOf"}
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { AIModelConfig } from "./constants/model.types";
2
+ import type { Hooks } from "./lib/hooks";
2
3
  import type { S3StorageConfig } from "./lib/storage";
3
4
  import type { WebhookConfig } from "./lib/webhook";
4
5
  export interface ProviderConfig {
@@ -64,6 +65,14 @@ export interface BrotuAIOptions {
64
65
  * later with `ai.webhook.set`. A per-request `webhook` on the params wins.
65
66
  */
66
67
  webhook?: string | WebhookConfig;
68
+ /**
69
+ * In-process callbacks, one per kind and stage: `onVideoLoading`,
70
+ * `onVideoSuccess`, `onVideoError`, and the same three for image, audio and
71
+ * text. Your logic runs inside your process, so it can send an email, write
72
+ * to your DB or push to a queue — no endpoint needed, unlike `webhook`.
73
+ * Throwing inside a hook never fails the generation.
74
+ */
75
+ hooks?: Hooks;
67
76
  }
68
77
  /** Resolved per-request routing: which host and key serve this model. */
69
78
  export interface ResolvedProvider {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IAClC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC9B;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC3C,mFAAmF;IACnF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC1D;;;;OAIG;IACH,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B;;;OAGG;IACH,WAAW,CAAC,EAAE,CACb,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,KACZ,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iEAAiE;IACjE,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;IACzB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC;CACjC;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CAChB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IAClC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC9B;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC3C,mFAAmF;IACnF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC1D;;;;OAIG;IACH,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B;;;OAGG;IACH,WAAW,CAAC,EAAE,CACb,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,KACZ,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iEAAiE;IACjE,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;IACzB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC;IACjC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC;CACd;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CAChB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brotu/ai",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "exports": {