@cartanova/qgrid-ai-sdk 2.15.5 → 2.16.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.ko.md CHANGED
@@ -394,7 +394,7 @@ Grok language model은 user 이미지 part(https URL 또는 inline bytes)를 버
394
394
  | Image | `n`(호출당 최대 3), `aspectRatio`, `xai.resolution`(`1k`/`1.5k`/`2k`), `xai.quality`, `xai.aspect_ratio`(`aspectRatio`가 없을 때) | 참조 `images`는 `referenceImages`(edit endpoint), `xai.output_format`은 `outputFormat`이 되며 `png`, `jpeg`, `webp`만 허용 | `size`, `seed`, `mask`, `xai.sync_mode`, `xai.user` |
395
395
  | Video | `duration`(정수 1-15), `aspectRatio`, `xai.resolution`(`480p`/`720p`/`1080p`), `xai.mode: "reference-to-video"`(지원하는 유일한 mode, `xai.referenceImageUrls` 필요), `xai.pollIntervalMs`, `xai.pollTimeoutMs` | `resolution` `WxH`는 `@ai-sdk/xai`와 같이 매핑(`1280x720` → `720p`), prompt `image`는 시작 프레임, `xai.referenceImageUrls`(7개가 아닌 최대 5개)는 `referenceImages`, `xai.lastFrame`(qgrid 확장, `grok-imagine-video-1.5` 전용) | `fps`, `seed`, `xai.keyframes`, `xai.storageOptions`, `xai.referenceVoiceIds`, `xai.user`. `xai.mode` `edit-video`/`extend-video`와 `xai.videoUrl`은 예외 |
396
396
  | Transcription | `xai.language` | 오디오 bytes는 base64로, `mediaType`은 파라미터 제거(`audio/webm;codecs=opus` → `audio/webm`) | 나머지 모든 `xai` 키(`audioFormat`, `sampleRate`, `format`, `diarize`, `keyterm`, ...) |
397
- | Speech | `voice`, `language` | `outputFormat`은 `format`(`mp3`, `wav`, `pcm`, `mulaw`, `alaw`, 그 외 값은 `@ai-sdk/xai`처럼 warning과 함께 `mp3`) | `instructions`, `speed`, 모든 `xai` 키(`sampleRate`, `bitRate`, `withTimestamps`, ...) |
397
+ | Speech | `voice`, `language`, `xai.withTimestamps` | `outputFormat`은 `format`(`mp3`, `wav`, `pcm`, `mulaw`, `alaw`, 그 외 값은 `@ai-sdk/xai`처럼 warning과 함께 `mp3`) | `instructions`, `speed`, 그 밖의 모든 `xai` 키(`sampleRate`, `bitRate`, `replace`, ...) |
398
398
 
399
399
  **요청 크기.** qgrid 서버는 최대 10 MiB JSON body를 받습니다. inline 미디어는 base64로 전송되어 3분의 1만큼
400
400
  커지므로, 서버는 inline 오디오, 각 inline 이미지, 한 요청의 inline 이미지 합계(비디오: `image` + `lastFrame` +
@@ -487,6 +487,7 @@ speech.audio.uint8Array;
487
487
 
488
488
  - `transcribe`는 bytes로 오디오 형식을 감지하고 실패하면 `audio/wav`를 씁니다. 서버는 `audio/*`가 아닌 값을 거부합니다. transcript에는 segment가 없습니다(`segments: []`).
489
489
  - 음성 합성 텍스트는 최대 15,000자입니다. `voice`, `language`, `outputFormat`을 지정하지 않으면 서버 기본값 `ara`, `en`, `mp3`가 쓰이며, live 검증된 조합은 이것뿐입니다. 다른 voice, language(`@ai-sdk/xai`의 `auto` 포함), codec은 그대로 전달되지만 미검증입니다. 서버가 돌려준 오디오 MIME 타입은 `providerMetadata.qgrid.mediaType`에 있고, AI SDK의 `speech.audio.mediaType`은 bytes에서 추정한 값입니다.
490
+ - `providerOptions: { xai: { withTimestamps: true } }`(2.16.0+, Grok 전용)를 주면 글자별 타이밍이 `providerMetadata.qgrid.audioTimestamps`에 담깁니다. `graphChars`는 입력 글자(공백·문장부호·음성 태그 포함), `graphTimes`는 같은 인덱스의 `[start, end]` 초이고, 전체 길이는 `providerMetadata.qgrid.duration`입니다. `@ai-sdk/xai`가 `providerMetadata.xai`에 담는 모양과 같습니다. 텍스트 정규화로 풀린 토큰은 첫 글자가 전체 구간을 가집니다. `qgrid.speech("openai/...")`는 타이밍 데이터가 없어 서버가 `withTimestamps`를 HTTP 400으로 거부합니다.
490
491
 
491
492
  ### OpenAI 음성 인식과 합성 (ChatGPT 구독)
492
493
 
package/README.md CHANGED
@@ -404,7 +404,7 @@ Grok language models keep user image parts (https URLs or inline bytes) instead
404
404
  | Image | `n` (up to 3 per call), `aspectRatio`, `xai.resolution` (`1k`/`1.5k`/`2k`), `xai.quality`, `xai.aspect_ratio` (used when `aspectRatio` is unset) | reference `images` become `referenceImages` (edit endpoint); `xai.output_format` becomes `outputFormat` and must be `png`, `jpeg`, or `webp` | `size`, `seed`, `mask`, `xai.sync_mode`, `xai.user` |
405
405
  | Video | `duration` (integer 1-15), `aspectRatio`, `xai.resolution` (`480p`/`720p`/`1080p`), `xai.mode: "reference-to-video"` (the only supported mode; needs `xai.referenceImageUrls`), `xai.pollIntervalMs`, `xai.pollTimeoutMs` | `resolution` `WxH` is mapped as in `@ai-sdk/xai` (`1280x720` to `720p`); prompt `image` becomes the start frame; `xai.referenceImageUrls` (up to 5, not 7) become `referenceImages`; `xai.lastFrame` (qgrid extension, `grok-imagine-video-1.5` only) | `fps`, `seed`, `xai.keyframes`, `xai.storageOptions`, `xai.referenceVoiceIds`, `xai.user`. `xai.mode` `edit-video`/`extend-video` and `xai.videoUrl` throw |
406
406
  | Transcription | `xai.language` | audio bytes become base64 and `mediaType` loses its parameters (`audio/webm;codecs=opus` to `audio/webm`) | every other `xai` key (`audioFormat`, `sampleRate`, `format`, `diarize`, `keyterm`, ...) |
407
- | Speech | `voice`, `language` | `outputFormat` becomes `format` (`mp3`, `wav`, `pcm`, `mulaw`, `alaw`; anything else falls back to `mp3` with a warning, as in `@ai-sdk/xai`) | `instructions`, `speed`, every `xai` key (`sampleRate`, `bitRate`, `withTimestamps`, ...) |
407
+ | Speech | `voice`, `language`, `xai.withTimestamps` | `outputFormat` becomes `format` (`mp3`, `wav`, `pcm`, `mulaw`, `alaw`; anything else falls back to `mp3` with a warning, as in `@ai-sdk/xai`) | `instructions`, `speed`, every other `xai` key (`sampleRate`, `bitRate`, `replace`, ...) |
408
408
 
409
409
  **Request size.** The qgrid server accepts JSON bodies up to 10 MiB. Inline media travels as
410
410
  base64, which grows by a third, so the server caps inline audio, each inline image, and all
@@ -499,6 +499,7 @@ speech.audio.uint8Array;
499
499
 
500
500
  - `transcribe` detects the audio type from its bytes and falls back to `audio/wav`; the server rejects anything that is not `audio/*`. Transcripts carry no segments (`segments: []`).
501
501
  - Speech text is limited to 15,000 characters. Unset `voice`, `language` and `outputFormat` use the server defaults `ara`, `en` and `mp3`, the only combination verified live. Other voices, languages (including `@ai-sdk/xai`'s `auto`) and codecs are passed through but unverified. The server's audio MIME type is in `providerMetadata.qgrid.mediaType`; AI SDK's `speech.audio.mediaType` is sniffed from the bytes.
502
+ - `providerOptions: { xai: { withTimestamps: true } }` (2.16.0+, Grok only) returns per-character timing in `providerMetadata.qgrid.audioTimestamps`: `graphChars` (input characters, spaces, punctuation and speech tags included) and the index-aligned `graphTimes` `[start, end]` seconds, plus `providerMetadata.qgrid.duration`. The shape matches what `@ai-sdk/xai` puts under `providerMetadata.xai`. Text normalization gives an expanded token's whole span to its first character. `qgrid.speech("openai/...")` has no timing data and the server rejects `withTimestamps` with HTTP 400.
502
503
 
503
504
  ### OpenAI transcription and speech (ChatGPT subscription)
504
505
 
package/dist/index.d.ts CHANGED
@@ -282,7 +282,9 @@ type QgridOpenAISpeechProviderOptions = {
282
282
  /**
283
283
  * Grok text-to-speech through `POST /api/qgrid/speak`. `voice`, `language` and `outputFormat`
284
284
  * map to the server's `voice`, `language` and `format`; when omitted the server applies
285
- * `ara` / `en` / `mp3` (the only combination verified live). The model id only labels the log.
285
+ * `ara` / `en` / `mp3`. The model id only labels the log. `providerOptions.xai.withTimestamps`
286
+ * returns per-character timing in `providerMetadata.qgrid.audioTimestamps` (`graphChars`,
287
+ * `graphTimes`), the shape `@ai-sdk/xai` puts under `providerMetadata.xai`.
286
288
  */
287
289
  declare function createQgridSpeech(modelId: QgridSpeechModel, config?: QgridMediaProviderConfig): SpeechModelV3;
288
290
  //#endregion
package/dist/index.js CHANGED
@@ -1478,7 +1478,7 @@ function isSpeechFormat(value) {
1478
1478
  }
1479
1479
  /**
1480
1480
  * `providerOptions.xai` for Grok speech, field-for-field from `@ai-sdk/xai`
1481
- * `xaiSpeechModelOptionsSchema`. The qgrid server takes none of them; every set key warns.
1481
+ * `xaiSpeechModelOptionsSchema`. Only `withTimestamps` is honored; every other set key warns.
1482
1482
  */
1483
1483
  const xaiSpeechOptionsSchema = z.object({
1484
1484
  sampleRate: z.union([
@@ -1520,11 +1520,18 @@ const OPENAI_SPEECH_FORMATS = ["wav", "pcm"];
1520
1520
  function isOpenAISpeechFormat(value) {
1521
1521
  return OPENAI_SPEECH_FORMATS.some((format) => format === value);
1522
1522
  }
1523
+ /** `providerOptions.xai` keys honored by `qgrid.speech("grok/...")`. */
1524
+ const XAI_SPEECH_HONORED = /* @__PURE__ */ new Set(["withTimestamps"]);
1523
1525
  const speakOutputSchema = z.object({
1524
1526
  audio: z.string(),
1525
1527
  mimeType: z.string(),
1526
1528
  tokenName: z.string(),
1527
- requestLogId: z.number().optional()
1529
+ requestLogId: z.number().optional(),
1530
+ audioTimestamps: z.object({
1531
+ graphChars: z.array(z.string()),
1532
+ graphTimes: z.array(z.tuple([z.number(), z.number()]))
1533
+ }).optional(),
1534
+ durationSec: z.number().optional()
1528
1535
  });
1529
1536
  function base64ToBytes(data) {
1530
1537
  if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(data, "base64"));
@@ -1536,7 +1543,9 @@ function base64ToBytes(data) {
1536
1543
  /**
1537
1544
  * Grok text-to-speech through `POST /api/qgrid/speak`. `voice`, `language` and `outputFormat`
1538
1545
  * map to the server's `voice`, `language` and `format`; when omitted the server applies
1539
- * `ara` / `en` / `mp3` (the only combination verified live). The model id only labels the log.
1546
+ * `ara` / `en` / `mp3`. The model id only labels the log. `providerOptions.xai.withTimestamps`
1547
+ * returns per-character timing in `providerMetadata.qgrid.audioTimestamps` (`graphChars`,
1548
+ * `graphTimes`), the shape `@ai-sdk/xai` puts under `providerMetadata.xai`.
1540
1549
  */
1541
1550
  function createQgridSpeech(modelId, config) {
1542
1551
  const provider = voiceProviderOf(modelId, LABEL$2);
@@ -1550,7 +1559,7 @@ function createQgridSpeech(modelId, config) {
1550
1559
  if (!options.text.trim()) throw new Error(`${LABEL$2}: text must not be empty`);
1551
1560
  if (options.text.length > MAX_TEXT_CHARS) throw new Error(`${LABEL$2}: text is limited to ${MAX_TEXT_CHARS} characters`);
1552
1561
  const qgridOptions = parseGrokQgridOptions(options.providerOptions?.qgrid, LABEL$2);
1553
- const xai = parseXaiMediaOptions(options.providerOptions?.xai, xaiSpeechOptionsSchema, /* @__PURE__ */ new Set(), LABEL$2);
1562
+ const xai = parseXaiMediaOptions(options.providerOptions?.xai, xaiSpeechOptionsSchema, XAI_SPEECH_HONORED, LABEL$2);
1554
1563
  const warnings = [];
1555
1564
  let format;
1556
1565
  if (options.outputFormat !== void 0) {
@@ -1580,6 +1589,7 @@ function createQgridSpeech(modelId, config) {
1580
1589
  if (options.voice !== void 0) requestParams.voice = options.voice;
1581
1590
  if (options.language !== void 0) requestParams.language = options.language;
1582
1591
  if (format !== void 0) requestParams.format = format;
1592
+ if (xai.options?.withTimestamps === true) requestParams.withTimestamps = true;
1583
1593
  if (projectName) requestParams.projectName = projectName;
1584
1594
  const params = {
1585
1595
  ...requestParams,
@@ -1601,6 +1611,8 @@ function createQgridSpeech(modelId, config) {
1601
1611
  tokenName: data.tokenName
1602
1612
  };
1603
1613
  if (data.requestLogId !== void 0) qgridMetadata.requestLogId = data.requestLogId;
1614
+ if (data.audioTimestamps) qgridMetadata.audioTimestamps = data.audioTimestamps;
1615
+ if (data.durationSec !== void 0) qgridMetadata.duration = data.durationSec;
1604
1616
  return {
1605
1617
  audio: base64ToBytes(data.audio),
1606
1618
  warnings,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cartanova/qgrid-ai-sdk",
3
- "version": "2.15.5",
3
+ "version": "2.16.0",
4
4
  "description": "AI SDK LanguageModelV3 and ImageModelV3 provider for qgrid",
5
5
  "repository": {
6
6
  "type": "git",