@broberg/ai-sdk 0.24.0 → 0.25.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
@@ -30,7 +30,7 @@ const emb = await ai.embedding({ text: ["a", "b"] });
30
30
 
31
31
  ## Capabilities
32
32
 
33
- `chat` · `vision` · `translate` · `image` (fal.ai) · `embedding` · `transcribe`
33
+ `chat` · `vision` · `translate` · `image` (fal.ai default / OpenRouter) · `embedding` · `transcribe`
34
34
  (Whisper), plus **prompt contracts** with structured output:
35
35
 
36
36
  ```ts
@@ -56,6 +56,14 @@ await ai.chat({ prompt: "…", tier: "powerful" });
56
56
  await ai.chat({ prompt: "…", override: { provider: "openrouter", model: "minimax/minimax-m2.7" } });
57
57
  ```
58
58
 
59
+ > **Images — raster vs. vector.** `ai.image()` defaults to fal.ai (raster PNG). For
60
+ > **vector/SVG** output (logos), override to OpenRouter Recraft — the slug is an
61
+ > **OpenRouter** model, not a fal app-id:
62
+ > ```ts
63
+ > await ai.image({ prompt: "…", override: { provider: "openrouter", model: "recraft/recraft-v4.1-vector" } });
64
+ > // → data:image/svg+xml;base64,… with ground-truth cost
65
+ > ```
66
+
59
67
  `cheap` defaults to the cheapest-that's-good-enough cloud model — **Mistral Small**
60
68
  (EU/Paris-hosted, GDPR-safe, ~$0.10/$0.30) — so a cost-tier call is safe for
61
69
  personal data by default; override per call for an even cheaper non-personal route.
package/dist/index.d.ts CHANGED
@@ -239,6 +239,20 @@ interface EmbeddingResult {
239
239
  vectors: number[][];
240
240
  usage: Usage;
241
241
  }
242
+ /** Timestamp granularity a caller can request from `ai.transcribe` (F036). */
243
+ type TimestampGranularity = "word" | "segment";
244
+ /** A single word with its start/end offset in seconds. */
245
+ interface WordTimestamp {
246
+ word: string;
247
+ start: number;
248
+ end: number;
249
+ }
250
+ /** A phrase/sentence segment with its start/end offset in seconds. */
251
+ interface SegmentTimestamp {
252
+ text: string;
253
+ start: number;
254
+ end: number;
255
+ }
242
256
  interface TranscribeRequest {
243
257
  /** Raw audio bytes (the client resolves a URL to bytes before calling). */
244
258
  audio: Uint8Array;
@@ -248,10 +262,17 @@ interface TranscribeRequest {
248
262
  /** Bias recognition toward these brand/jargon terms (Azure phraseList, F029.3).
249
263
  * Providers without biasing support (Voxtral/Whisper) ignore it. */
250
264
  phrases?: string[];
265
+ /** Request timestamps (F036). The client normalizes the input to this array.
266
+ * Adapters without timestamp support ignore it (result omits words/segments). */
267
+ timestamps?: TimestampGranularity[];
251
268
  spec: TierSpec;
252
269
  }
253
270
  interface TranscribeResult {
254
271
  text: string;
272
+ /** Word-level timing — present only when "word" timestamps were requested. */
273
+ words?: WordTimestamp[];
274
+ /** Segment (phrase/sentence) timing — present when any timestamps were requested. */
275
+ segments?: SegmentTimestamp[];
255
276
  usage: Usage;
256
277
  }
257
278
  interface OcrRequest {
@@ -1331,6 +1352,9 @@ declare const transcribeInputSchema: z.ZodObject<{
1331
1352
  durationSec: z.ZodOptional<z.ZodNumber>;
1332
1353
  /** Bias toward brand/jargon terms (Azure phraseList, F029.3); others ignore it. */
1333
1354
  phrases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1355
+ /** Opt-in timestamps (F036) — a single granularity or an array. Omit → the
1356
+ * current { text, usage } shape, unchanged. Pass ["word","segment"] for both. */
1357
+ timestamps: z.ZodOptional<z.ZodUnion<[z.ZodEnum<["word", "segment"]>, z.ZodArray<z.ZodEnum<["word", "segment"]>, "many">]>>;
1334
1358
  }, "strip", z.ZodTypeAny, {
1335
1359
  audio: string | Uint8Array<ArrayBuffer>;
1336
1360
  language?: string | undefined;
@@ -1349,6 +1373,7 @@ declare const transcribeInputSchema: z.ZodObject<{
1349
1373
  labels?: Record<string, string> | undefined;
1350
1374
  durationSec?: number | undefined;
1351
1375
  phrases?: string[] | undefined;
1376
+ timestamps?: "word" | "segment" | ("word" | "segment")[] | undefined;
1352
1377
  }, {
1353
1378
  audio: string | Uint8Array<ArrayBuffer>;
1354
1379
  language?: string | undefined;
@@ -1367,6 +1392,7 @@ declare const transcribeInputSchema: z.ZodObject<{
1367
1392
  labels?: Record<string, string> | undefined;
1368
1393
  durationSec?: number | undefined;
1369
1394
  phrases?: string[] | undefined;
1395
+ timestamps?: "word" | "segment" | ("word" | "segment")[] | undefined;
1370
1396
  }>;
1371
1397
  declare const ocrInputSchema: z.ZodObject<{
1372
1398
  tier: z.ZodOptional<z.ZodEnum<["fast", "smart", "powerful", "cheap", "vision", "video", "embedding"]>>;
@@ -2017,8 +2043,8 @@ declare const falStubAdapter: ProviderAdapter;
2017
2043
  * wires the live adapters. */
2018
2044
  declare const stubProviders: Record<string, ProviderAdapter>;
2019
2045
 
2020
- declare const VERSION: "0.24.0";
2021
- declare const SDK_TAG: "@broberg/ai-sdk@0.24.0";
2046
+ declare const VERSION: "0.25.0";
2047
+ declare const SDK_TAG: "@broberg/ai-sdk@0.25.0";
2022
2048
 
2023
2049
  /** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
2024
2050
  * per-call override.
package/dist/index.js CHANGED
@@ -730,6 +730,10 @@ function openaiAdapter(config = {}) {
730
730
  form.append("file", new Blob([req.audio]), "audio");
731
731
  form.append("model", req.spec.model);
732
732
  if (req.language) form.append("language", req.language);
733
+ if (req.timestamps && req.timestamps.length > 0) {
734
+ form.append("response_format", "verbose_json");
735
+ for (const g of req.timestamps) form.append("timestamp_granularities[]", g);
736
+ }
733
737
  const fetchImpl = config.fetch ?? fetch;
734
738
  const res = await fetchImpl(`${baseUrl}/audio/transcriptions`, {
735
739
  method: "POST",
@@ -753,7 +757,14 @@ function openaiAdapter(config = {}) {
753
757
  const perMinute = WHISPER_PRICE_PER_MIN[req.spec.model] ?? 0;
754
758
  usage.costUsd = req.durationSec / 60 * perMinute;
755
759
  }
756
- return { text: data.text ?? "", usage };
760
+ const result = { text: data.text ?? "", usage };
761
+ if (req.timestamps?.includes("word") && data.words) {
762
+ result.words = data.words.map((w) => ({ word: w.word, start: w.start, end: w.end }));
763
+ }
764
+ if (req.timestamps && req.timestamps.length > 0 && data.segments) {
765
+ result.segments = data.segments.map((s) => ({ text: s.text, start: s.start, end: s.end }));
766
+ }
767
+ return result;
757
768
  }
758
769
  return { ...base, embedding, transcribe };
759
770
  }
@@ -2526,6 +2537,9 @@ var transcribeInputSchema = z.object({
2526
2537
  durationSec: z.number().positive().optional(),
2527
2538
  /** Bias toward brand/jargon terms (Azure phraseList, F029.3); others ignore it. */
2528
2539
  phrases: z.array(z.string()).optional(),
2540
+ /** Opt-in timestamps (F036) — a single granularity or an array. Omit → the
2541
+ * current { text, usage } shape, unchanged. Pass ["word","segment"] for both. */
2542
+ timestamps: z.union([z.enum(["word", "segment"]), z.array(z.enum(["word", "segment"]))]).optional(),
2529
2543
  ...callOptions
2530
2544
  });
2531
2545
  var ocrInputSchema = z.object({
@@ -2572,8 +2586,8 @@ var aiConfigSchema = z.object({
2572
2586
  });
2573
2587
 
2574
2588
  // src/version.ts
2575
- var VERSION = "0.24.0";
2576
- var SDK_TAG = "@broberg/ai-sdk@0.24.0";
2589
+ var VERSION = "0.25.0";
2590
+ var SDK_TAG = "@broberg/ai-sdk@0.25.0";
2577
2591
 
2578
2592
  // src/cost/sinks/upmetrics.ts
2579
2593
  function upmetricsSink(config) {
@@ -3126,7 +3140,8 @@ function createAI(config = {}) {
3126
3140
  invoke: async (spec) => {
3127
3141
  const adapter = pickProvider(spec.provider);
3128
3142
  if (!adapter.transcribe) throw new Error(`createAI: provider "${spec.provider}" does not support transcribe`);
3129
- return adapter.transcribe({ audio, language: input.language, durationSec: input.durationSec, phrases: input.phrases, spec });
3143
+ const timestamps = input.timestamps === void 0 ? void 0 : Array.isArray(input.timestamps) ? input.timestamps : [input.timestamps];
3144
+ return adapter.transcribe({ audio, language: input.language, durationSec: input.durationSec, phrases: input.phrases, timestamps, spec });
3130
3145
  }
3131
3146
  });
3132
3147
  },