@cartanova/qgrid-ai-sdk 2.18.0 → 2.19.1

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
@@ -396,9 +396,9 @@ Grok language model은 user 이미지 part(https URL 또는 inline bytes)를 버
396
396
  | Transcription | `xai.language` | 오디오 bytes는 base64로, `mediaType`은 파라미터 제거(`audio/webm;codecs=opus` → `audio/webm`) | 나머지 모든 `xai` 키(`audioFormat`, `sampleRate`, `format`, `diarize`, `keyterm`, ...) |
397
397
  | Speech | `voice`, `language`, `speed`(0.7-1.5, 2.18.0+), `xai.withTimestamps` | `outputFormat`은 `format`(`mp3`, `wav`, `pcm`, `mulaw`, `alaw`, 그 외 값은 `@ai-sdk/xai`처럼 warning과 함께 `mp3`) | `instructions`, 그 밖의 모든 `xai` 키(`sampleRate`, `bitRate`, `replace`, ...) |
398
398
 
399
- **요청 크기.** qgrid 서버는 최대 10 MiB JSON body를 받습니다. inline 미디어는 base64로 전송되어 3분의 1만큼
399
+ **요청 크기.** qgrid 서버는 최대 26 MiB JSON body를 받습니다. inline 미디어는 base64로 전송되어 3분의 1만큼
400
400
  커지므로, 서버는 inline 오디오, 각 inline 이미지, 한 요청의 inline 이미지 합계(비디오: `image` + `lastFrame` +
401
- 참조 이미지)를 디코딩 기준 7 MiB로 제한합니다. SDK는 더 큰 입력을 전송 전에 거부하고, 서버 413도 같은 크기
401
+ 참조 이미지)를 디코딩 기준 19 MiB로 제한합니다(2.19.0+, 이전 서버와 SDK는 10 MiB와 7 MiB). SDK는 더 큰 입력을 전송 전에 거부하고, 서버 413도 같은 크기
402
402
  오류로 보여줍니다. 입력을 압축하거나, 옵션이 허용하면 https 이미지 URL을 넘기세요.
403
403
 
404
404
  ### Grok 이미지
@@ -485,9 +485,9 @@ const speech = await generateSpeech({
485
485
  speech.audio.uint8Array;
486
486
  ```
487
487
 
488
- - `transcribe`는 bytes로 오디오 형식을 감지하고 실패하면 `audio/wav`를 씁니다. 서버는 `audio/*`가 아닌 값을 거부합니다. Grok transcript는 들린 단어마다 segment 하나를 담습니다(`segments: [{ text, startSecond, endSecond }]`, 2.17.0+). OpenAI realtime transcript에는 없습니다.
488
+ - `transcribe`는 bytes로 오디오 형식을 감지하고 실패하면 `audio/wav`를 씁니다. 서버는 `audio/*`가 아닌 값을 거부합니다. transcript는 들린 단어마다 segment 하나를 담습니다(`segments: [{ text, startSecond, endSecond }]`). Grok은 직접 시간을 재고(2.17.0+), OpenAI는 서버가 내장 정렬기로 transcript를 오디오에 맞춥니다(서버 2.19.0+). 정렬에는 클립 길이의 약 5%만큼 시간이 더 듭니다. 서버에 정렬기가 없거나 클립이 정렬 한도(빽빽한 음성 기준 약 4분)를 넘으면 OpenAI segment는 비어 있습니다.
489
489
  - 음성 합성 텍스트는 최대 15,000자입니다. `voice`, `language`, `outputFormat`을 지정하지 않으면 서버 기본값 `ara`, `en`, `mp3`가 쓰입니다. 구독 토큰에 보이는 보이스 28종(여성 9, 남성 19. 예: `ara`, `eve`, `rex`)은 2026-11-09에 모두 영어·mp3로 live 생성했습니다. 다른 language(`@ai-sdk/xai`의 `auto` 포함)와 codec은 그대로 전달되지만 미검증입니다. xAI 목록의 음성 태그(`[pause]`, `[laugh]`, `<whisper>...</whisper>` 등)는 2026-11 확인에서 글자로 읽히지 않았고, `[deadpan]` 같은 ElevenLabs식 감정 태그는 그 목록에 없습니다. `speed`는 말하기 속도입니다(1이 보통, 0.7-1.5 밖의 값은 서버가 거부). 서버가 돌려준 오디오 MIME 타입은 `providerMetadata.qgrid.mediaType`에 있고, AI SDK의 `speech.audio.mediaType`은 bytes에서 추정한 값입니다.
490
- - 단어 타이밍: `providerOptions: { xai: { withTimestamps: true } }`(2.16.0+, Grok) 또는 `providerOptions: { qgrid: { withTimestamps: true } }`(2.17.0+, Grok·OpenAI)를 주면 글자별 타이밍이 `providerMetadata.qgrid.audioTimestamps`에 담깁니다. `graphChars`는 입력 글자(공백·문장부호·음성 태그 포함), `graphTimes`는 같은 인덱스의 `[start, end]` 초이고, 전체 길이는 `providerMetadata.qgrid.duration`입니다. `@ai-sdk/xai`가 `providerMetadata.xai`에 담는 모양과 같습니다. `providerMetadata.qgrid.timestampsSource`는 Grok이면 `"provider"`(Grok이 직접 잰 값), OpenAI면 `"aligned"`입니다. OpenAI는 서버가 생성된 오디오를 Grok STT로 받아쓰고 단어 단위로 원문과 짝지어 만듭니다(못 들은 단어는 앞뒤 단어 사이 구간을 나눠 가짐). 타이밍 단계가 실패해도 오디오는 돌려주고, 메타데이터에 `timestampsError`와 AI SDK warning을 남깁니다. 텍스트 정규화로 풀린 토큰은 첫 글자가 전체 구간을 가집니다.
490
+ - 단어 타이밍: `providerOptions: { xai: { withTimestamps: true } }`(2.16.0+, Grok) 또는 `providerOptions: { qgrid: { withTimestamps: true } }`(2.17.0+, Grok·OpenAI)를 주면 글자별 타이밍이 `providerMetadata.qgrid.audioTimestamps`에 담깁니다. `graphChars`는 입력 글자(공백·문장부호·음성 태그 포함), `graphTimes`는 같은 인덱스의 `[start, end]` 초이고, 전체 길이는 `providerMetadata.qgrid.duration`입니다. `@ai-sdk/xai`가 `providerMetadata.xai`에 담는 모양과 같습니다. `providerMetadata.qgrid.timestampsSource`는 Grok이면 `"provider"`(Grok이 직접 잰 값), OpenAI면 `"aligned"`입니다. OpenAI는 서버가 내장 정렬기로 생성된 오디오에 원문을 맞춰 만듭니다(서버 2.19.0+, 이전 서버는 Grok STT를 거쳤음). 정렬기는 qgrid Docker 이미지에 들어 있고 npm이나 pnpm 설치에서는 `qgrid --install-aligner`로 설치합니다. 정렬기가 없는 서버와 정렬기 한도(빽빽한 음성 기준 약 4분)를 넘는 긴 음성은 `timestampsError`를 돌려줍니다(오류에 그 대본의 한도가 나옴). 타이밍 단계가 실패해도 오디오는 돌려주고, 메타데이터에 `timestampsError`와 AI SDK warning을 남깁니다. 텍스트 정규화로 풀린 토큰은 첫 글자가 전체 구간을 가집니다. Grok mp3에는 앞부분 무음 정보(gapless 태그)가 없어 재생기가 인코더 지연을 그대로 재생하므로, 서버 2.19.0부터 qgrid가 Grok 타이밍에 그 46ms를 더해 실제 재생과 맞춥니다(`@ai-sdk/xai`의 원래 값보다 46ms 늦음).
491
491
 
492
492
  ### OpenAI 음성 인식과 합성 (ChatGPT 구독)
493
493
 
@@ -511,10 +511,10 @@ const speech = await generateSpeech({
511
511
 
512
512
  | 모델 | 그대로 전달(`@ai-sdk/openai` 이름) | 변환 | 미지원(경고) |
513
513
  | --- | --- | --- | --- |
514
- | Transcription | `openai.language`, `openai.prompt`(`gpt-realtime-whisper`는 HTTP 400으로 거부, `whisper-1`은 받지만 업스트림이 무시) | 입력은 PCM16 WAV(`audio/wav`) 또는 raw PCM(`audio/pcm;rate=…;channels=…`)만 가능하며 mp3/webm/ogg는 디코더가 추가되기 전까지 서버가 HTTP 400으로 거부합니다. `durationInSeconds`는 보낸 오디오에서 계산하고 `language`는 요청값을 그대로 돌려줍니다(realtime 세션은 둘 다 보고하지 않음). `segments`는 비어 있습니다 | `openai.temperature`, `openai.include`, `openai.timestampGranularities`, `openai.responseFormat`, `openai.chunkingStrategy`, `openai.streaming` |
514
+ | Transcription | `openai.language`, `openai.prompt`(`gpt-realtime-whisper`는 HTTP 400으로 거부, `whisper-1`은 받지만 업스트림이 무시) | 입력은 PCM16 WAV(`audio/wav`) 또는 raw PCM(`audio/pcm;rate=…;channels=…`)만 가능하며 mp3/webm/ogg는 디코더가 추가되기 전까지 서버가 HTTP 400으로 거부합니다. `durationInSeconds`는 보낸 오디오에서 계산하고 `language`는 요청값을 그대로 돌려줍니다(realtime 세션은 둘 다 보고하지 않음). `segments`에는 정렬한 단어 타이밍이 담깁니다(서버 2.19.0+) | `openai.temperature`, `openai.include`, `openai.timestampGranularities`, `openai.responseFormat`, `openai.chunkingStrategy`, `openai.streaming` |
515
515
  | Speech | `voice`(기본 `marin`; Codex v2 목록 `alloy ash ballad coral echo sage shimmer verse marin cedar`, 그 외는 HTTP 400), `instructions` / `openai.instructions`(원문 그대로 읽기 지시문 뒤에 붙는 스타일 안내) | `outputFormat`은 `wav`(기본, PCM16 24 kHz) 또는 `pcm`; 그 외 값은 `@ai-sdk/openai`가 `mp3`로 되돌리듯 경고와 함께 `wav`로 바뀝니다 | `language`, `speed`, `openai.speed` |
516
516
 
517
- - 음성 인식은 먼저 Codex realtime call에서 25초 예산으로 실행됩니다(call은 생성 약 30초 뒤 정리됨). 그 안에 전사가 오지 않으면 서버가 직접 realtime 전사 소켓으로 1회 재시도합니다(최대 60초). 7 MiB 인라인 제한(24 kHz PCM 약 2.5분)은 보통 첫 경로에서 몇 초 안에 끝납니다.
517
+ - 음성 인식은 먼저 Codex realtime call에서 25초 예산으로 실행됩니다(call은 생성 약 30초 뒤 정리됨). 그 안에 전사가 오지 않으면 서버가 직접 realtime 전사 소켓으로 1회 재시도합니다(최대 60초). 19 MiB 인라인 제한에는 24 kHz mono WAV가 약 6.9분 들어갑니다. 6.7분 영어 클립은 첫 경로에서 약 20초에 끝났고, transcript가 더 길게 나온 6.4분 클립은 기한을 넘겨 재시도 경로에서 52초에 끝났습니다.
518
518
  - 음성 합성은 TTS 모델이 아니라 텍스트를 읽는 대화 모델입니다. qgrid는 `<script>` 태그 안의 텍스트만 읽고 답하지 말라고 지시합니다. 원문 그대로 읽는지는 짧은 영어 문장과 질문형 영어 캐릭터 대사에서 검증했습니다. 모델이 실제로 말한 transcript를 요청 로그에 함께 남겨 비교할 수 있지만, 한영 혼합 텍스트에서는 transcript가 일치해도 영어 음성을 알아듣기 어려운 경우가 있었습니다.
519
519
  - 모델별 동작은 서버 저장소의 `docs/diagnosis/2026-10-17-openai-realtime-audio-probe.md`에 기록되어 있습니다. 2026-10-17 기준 알려진 한계: `gpt-live-transcribe`, `gpt-realtime-whisper`는 60초 클립의 절반 정도만 반환하므로 짧은 발화에 쓰세요. `gpt-realtime-whisper`는 `openai.prompt`를 거부합니다(서버가 업스트림 호출 전에 HTTP 400). `whisper-1`은 `openai.prompt`를 받지만 무시합니다. 음성 합성은 `gpt-realtime-2.1-mini`가 모든 테스트 문장을 그대로 읽었고(13/13), `gpt-realtime-2.1`은 1회 의역(12/13)했습니다. `gpt-realtime-1.5`는 텍스트를 읽지 않고 답변하는 경우가 잦아(6/13) 2.18.0에서 제외했습니다. 2026-11-06에는 현재 프롬프트로 `gpt-realtime-2.1`과 `gpt-realtime-2.1-mini` 모두 질문형 캐릭터 대사 12/12를 그대로 읽었습니다(이전 프롬프트에서는 `gpt-realtime-2.1`이 7/12를 답변). 2026-11-09에 독립 STT 2종으로 영어 108건(6문장, 보이스 `marin` `cedar` `ballad`, 각 3회)을 판정한 결과 `gpt-realtime-2.1`은 54/54, `gpt-realtime-2.1-mini`는 53/54였고, 실패 1건은 "Sure, let me read that out loud for you exactly as written."으로 시작했습니다. 한영 혼합 문장은 두 모델 모두 끝까지 읽었습니다(각 9/9). 모델이 남기는 transcript에는 이런 서두가 나타나지 않으므로, 정확성이 중요하면 transcript 대신 STT나 `withTimestamps`(정렬이 덧붙은 말을 건너뜀)로 오디오를 확인하세요.
520
520
 
package/README.md CHANGED
@@ -406,10 +406,10 @@ Grok language models keep user image parts (https URLs or inline bytes) instead
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
407
  | Speech | `voice`, `language`, `speed` (0.7-1.5, 2.18.0+), `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`, every other `xai` key (`sampleRate`, `bitRate`, `replace`, ...) |
408
408
 
409
- **Request size.** The qgrid server accepts JSON bodies up to 10 MiB. Inline media travels as
409
+ **Request size.** The qgrid server accepts JSON bodies up to 26 MiB. Inline media travels as
410
410
  base64, which grows by a third, so the server caps inline audio, each inline image, and all
411
- inline images of one request together (video: `image` + `lastFrame` + references) at 7 MiB
412
- decoded. The SDK rejects larger input before transport; a server 413 surfaces as the same size
411
+ inline images of one request together (video: `image` + `lastFrame` + references) at 19 MiB
412
+ decoded (2.19.0+; earlier servers and SDKs use 10 MiB and 7 MiB). The SDK rejects larger input before transport; a server 413 surfaces as the same size
413
413
  error. Compress the input, or pass an https image URL where the option accepts one.
414
414
 
415
415
  ### Grok images
@@ -497,9 +497,9 @@ const speech = await generateSpeech({
497
497
  speech.audio.uint8Array;
498
498
  ```
499
499
 
500
- - `transcribe` detects the audio type from its bytes and falls back to `audio/wav`; the server rejects anything that is not `audio/*`. Grok transcripts carry one segment per heard word (`segments: [{ text, startSecond, endSecond }]`, 2.17.0+); OpenAI realtime transcripts carry none.
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 one segment per heard word (`segments: [{ text, startSecond, endSecond }]`): Grok times them itself (2.17.0+); for OpenAI the server force-aligns the transcript to the audio with its built-in aligner (server 2.19.0+), adding about 5% of the clip length to the request. OpenAI segments are empty when the server lacks the aligner or the clip is too long to align (about 4 minutes of dense speech).
501
501
  - Speech text is limited to 15,000 characters. Unset `voice`, `language` and `outputFormat` use the server defaults `ara`, `en` and `mp3`. All 28 voices the subscription token lists (9 female, 19 male, such as `ara`, `eve`, `rex`) were generated live in English as mp3 on 2026-11-09; other languages (including `@ai-sdk/xai`'s `auto`) and codecs are passed through but unverified. Speech tags from xAI's list (`[pause]`, `[laugh]`, `<whisper>...</whisper>`, ...) were not read aloud in a 2026-11 check; ElevenLabs-style emotion tags such as `[deadpan]` are not in that list. `speed` is the speaking rate (1 is normal; the server rejects values outside 0.7-1.5). The server's audio MIME type is in `providerMetadata.qgrid.mediaType`; AI SDK's `speech.audio.mediaType` is sniffed from the bytes.
502
- - Word timing: `providerOptions: { xai: { withTimestamps: true } }` (2.16.0+, Grok) or `providerOptions: { qgrid: { withTimestamps: true } }` (2.17.0+, Grok or OpenAI) 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`. `providerMetadata.qgrid.timestampsSource` is `"provider"` for Grok, which times its own audio, and `"aligned"` for OpenAI, whose audio the server transcribes with Grok STT and aligns word by word to the text (unheard words share the gap between neighbours). If the timing step fails the audio is still returned, with `timestampsError` in the metadata and an AI SDK warning. Text normalization gives an expanded token's whole span to its first character.
502
+ - Word timing: `providerOptions: { xai: { withTimestamps: true } }` (2.16.0+, Grok) or `providerOptions: { qgrid: { withTimestamps: true } }` (2.17.0+, Grok or OpenAI) 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`. `providerMetadata.qgrid.timestampsSource` is `"provider"` for Grok, which times its own audio, and `"aligned"` for OpenAI, whose audio the server force-aligns to the text with its built-in aligner (server 2.19.0+; earlier servers used a Grok STT pass). The aligner ships in the qgrid Docker image and `qgrid --install-aligner` installs it on an npm or pnpm install; a server without it returns `timestampsError`, as does speech longer than the aligner's limit (about 4 minutes of dense speech; the error states the limit for that script). If the timing step fails the audio is still returned, with `timestampsError` in the metadata and an AI SDK warning. Text normalization gives an expanded token's whole span to its first character. Grok's mp3 carries no gapless tag, so players keep the encoder delay; since server 2.19.0 qgrid adds those 46 ms to Grok timing so it matches what plays (raw `@ai-sdk/xai` values are 46 ms earlier).
503
503
 
504
504
  ### OpenAI transcription and speech (ChatGPT subscription)
505
505
 
@@ -523,10 +523,10 @@ const speech = await generateSpeech({
523
523
 
524
524
  | Model | Faithful (`@ai-sdk/openai` names) | Transformed | Unsupported (warning) |
525
525
  | --- | --- | --- | --- |
526
- | Transcription | `openai.language`, `openai.prompt` (not on `gpt-realtime-whisper`, which rejects it with HTTP 400; accepted but ignored upstream on `whisper-1`) | input must be PCM16 WAV (`audio/wav`) or raw PCM (`audio/pcm;rate=…;channels=…`); mp3/webm/ogg are rejected by the server with HTTP 400 until a decoder lands. `durationInSeconds` is computed from the audio sent; `language` echoes the request (the realtime session reports neither); `segments` is empty | `openai.temperature`, `openai.include`, `openai.timestampGranularities`, `openai.responseFormat`, `openai.chunkingStrategy`, `openai.streaming` |
526
+ | Transcription | `openai.language`, `openai.prompt` (not on `gpt-realtime-whisper`, which rejects it with HTTP 400; accepted but ignored upstream on `whisper-1`) | input must be PCM16 WAV (`audio/wav`) or raw PCM (`audio/pcm;rate=…;channels=…`); mp3/webm/ogg are rejected by the server with HTTP 400 until a decoder lands. `durationInSeconds` is computed from the audio sent; `language` echoes the request (the realtime session reports neither); `segments` carries aligned word timing (server 2.19.0+) | `openai.temperature`, `openai.include`, `openai.timestampGranularities`, `openai.responseFormat`, `openai.chunkingStrategy`, `openai.streaming` |
527
527
  | Speech | `voice` (default `marin`; Codex v2 list `alloy ash ballad coral echo sage shimmer verse marin cedar`, others fail with HTTP 400), `instructions` / `openai.instructions` (style guidance appended to the verbatim-repeat prompt) | `outputFormat` `wav` (default, PCM16 24 kHz) or `pcm`; anything else falls back to `wav` with a warning, as `@ai-sdk/openai` falls back to `mp3` | `language`, `speed`, `openai.speed` |
528
528
 
529
- - Each transcription first runs on the Codex realtime call with a 25 s budget (the call is torn down about 30 s after creation); if that window closes before the transcript arrives, the server retries once on the direct realtime transcription socket (up to 60 s). The 7 MiB inline limit (~2.5 min of 24 kHz PCM) normally completes in a few seconds on the first path.
529
+ - Each transcription first runs on the Codex realtime call with a 25 s budget (the call is torn down about 30 s after creation); if that window closes before the transcript arrives, the server retries once on the direct realtime transcription socket (up to 60 s). The 19 MiB inline limit holds about 6.9 min of 24 kHz mono WAV. A 6.7 min English clip finished on the first path in about 20 s; a 6.4 min clip that produced a longer transcript missed the window and finished on the retry after 52 s.
530
530
  - Speech is a conversational model reading text, not a TTS model. qgrid's prompt tells it to read only the text inside `<script>` tags and never answer it. Verbatim fidelity was verified on short English sentences and on question-form English character lines. The model's own transcript of what it said is logged with the request for comparison, but for mixed Korean/English text a matching transcript did not guarantee intelligible English audio.
531
531
  - Per-model behavior is recorded in the server repository's `docs/diagnosis/2026-10-17-openai-realtime-audio-probe.md`. Known limits on 2026-10-17: `gpt-live-transcribe` and `gpt-realtime-whisper` returned roughly half of a 60 s clip (use them for short utterances); `gpt-realtime-whisper` rejects `openai.prompt` (the server answers HTTP 400 before any upstream call); `whisper-1` accepts but ignores `openai.prompt`. For speech, `gpt-realtime-2.1-mini` repeated every test text verbatim (13/13), `gpt-realtime-2.1` paraphrased once (12/13); `gpt-realtime-1.5` often answered the text instead of repeating it (6/13) and was removed in 2.18.0. On 2026-11-06, with the current prompt, `gpt-realtime-2.1` and `gpt-realtime-2.1-mini` both read 12/12 question-form character lines verbatim (the earlier prompt let `gpt-realtime-2.1` answer 7/12). On 2026-11-09, judged by two independent STT models over 108 English clips (6 lines, voices `marin` `cedar` `ballad`, 3 takes each), `gpt-realtime-2.1` read 54/54 and `gpt-realtime-2.1-mini` 53/54: one take began with "Sure, let me read that out loud for you exactly as written." Both read every Korean/English mixed line in full (9/9 each). The model's own logged transcript does not show such a preamble, so when fidelity matters, check the audio with STT or `withTimestamps` (its alignment skips the extra words) instead of the transcript.
532
532
 
package/dist/index.js CHANGED
@@ -989,7 +989,7 @@ function imageInputUrl(file) {
989
989
  return `data:${file.mediaType};base64,${data}`;
990
990
  }
991
991
  /** Server-side Fastify body limit (`sonamu.config.ts` `bodyLimit`). Larger bodies get a 413. */
992
- const QGRID_MAX_REQUEST_BODY_BYTES = 10485760;
992
+ const QGRID_MAX_REQUEST_BODY_BYTES = 27262976;
993
993
  function resolveMediaConfig(config) {
994
994
  return {
995
995
  serverUrl: config?.serverUrl ?? process.env.QGRID_URL ?? "http://localhost:44900",
@@ -1026,10 +1026,10 @@ function formatMiB(bytes) {
1026
1026
  }
1027
1027
  /**
1028
1028
  * Server `MEDIA_MAX_INLINE_BYTES` (qgrid.types.ts): decoded inline audio, each inline image, and all
1029
- * inline images of one request together. Derived from the 10 MiB body limit: base64 grows bytes by
1030
- * 4/3, so 7 MiB decoded is about 9.3 MiB encoded, leaving room for the rest of the JSON body.
1029
+ * inline images of one request together. Derived from the 26 MiB body limit: base64 grows bytes by
1030
+ * 4/3, so 19 MiB decoded is about 25.3 MiB encoded, leaving room for the rest of the JSON body.
1031
1031
  */
1032
- const QGRID_MAX_INLINE_MEDIA_BYTES = 7340032;
1032
+ const QGRID_MAX_INLINE_MEDIA_BYTES = 19922944;
1033
1033
  function base64DecodedBytes(data) {
1034
1034
  const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
1035
1035
  return Math.floor(data.length * 3 / 4) - padding;
@@ -1045,7 +1045,7 @@ function assertInlineMediaFits(items, label) {
1045
1045
  const comma = item.startsWith("data:") ? item.indexOf(",") : -1;
1046
1046
  total += base64DecodedBytes(comma >= 0 ? item.slice(comma + 1) : item);
1047
1047
  }
1048
- if (total > 7340032) throw new Error(`${label}: inline media is ${formatMiB(total)}, above the qgrid server limit of ${formatMiB(QGRID_MAX_INLINE_MEDIA_BYTES)} per request; compress it or pass an https URL where supported.`);
1048
+ if (total > 19922944) throw new Error(`${label}: inline media is ${formatMiB(total)}, above the qgrid server limit of ${formatMiB(QGRID_MAX_INLINE_MEDIA_BYTES)} per request; compress it or pass an https URL where supported.`);
1049
1049
  }
1050
1050
  /**
1051
1051
  * Serializes a Sonamu `params` body and rejects it before transport when it would exceed the
@@ -1054,7 +1054,7 @@ function assertInlineMediaFits(items, label) {
1054
1054
  function serializeMediaBody(params, label) {
1055
1055
  const body = JSON.stringify({ params });
1056
1056
  const bytes = new TextEncoder().encode(body).byteLength;
1057
- if (bytes > 10485760) throw new Error(`${label}: request body is ${formatMiB(bytes)}, above the qgrid server limit of ${formatMiB(QGRID_MAX_REQUEST_BODY_BYTES)}; compress inline media or pass an https URL where supported.`);
1057
+ if (bytes > 27262976) throw new Error(`${label}: request body is ${formatMiB(bytes)}, above the qgrid server limit of ${formatMiB(QGRID_MAX_REQUEST_BODY_BYTES)}; compress inline media or pass an https URL where supported.`);
1058
1058
  return body;
1059
1059
  }
1060
1060
  const ERROR_BODY_PREVIEW_CHARS = 2e3;
@@ -1652,8 +1652,8 @@ function createQgridSpeech(modelId, config) {
1652
1652
  * (default `marin`, Codex v2 list), `outputFormat` `wav` (default) or `pcm` — other formats
1653
1653
  * warn and fall back to wav, as the official provider falls back to mp3 — `openai.instructions`
1654
1654
  * as style guidance, `openai.speed` and `language` unsupported with the official warning text.
1655
- * `providerOptions.qgrid.withTimestamps` returns timing the server infers by aligning a Grok STT
1656
- * pass over the generated audio (`providerMetadata.qgrid.timestampsSource: "aligned"`).
1655
+ * `providerOptions.qgrid.withTimestamps` returns timing the server's built-in aligner places in the
1656
+ * generated audio (`providerMetadata.qgrid.timestampsSource: "aligned"`).
1657
1657
  */
1658
1658
  function createOpenAISpeech(modelId, serverUrl, projectName) {
1659
1659
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cartanova/qgrid-ai-sdk",
3
- "version": "2.18.0",
3
+ "version": "2.19.1",
4
4
  "description": "AI SDK LanguageModelV3 and ImageModelV3 provider for qgrid",
5
5
  "repository": {
6
6
  "type": "git",