@cartanova/qgrid-ai-sdk 2.15.3 → 2.15.4
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 +31 -2
- package/README.md +31 -2
- package/dist/index.d.ts +41 -3
- package/dist/index.js +181 -11
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -347,8 +347,8 @@ qgrid는 `grok/*` 모델을 qgrid 대시보드에 등록한 xAI 구독 토큰(
|
|
|
347
347
|
| `qgrid(id)` | `grok/grok-4.7`(카탈로그 기본 모델), `grok/grok-4.7-build-fast`, `grok/grok-4.6`, `grok/grok-4.5`, 그 밖의 `grok/<id>` | `generateText`, `streamText` |
|
|
348
348
|
| `qgrid.image(id)` | `grok/grok-imagine-image`, `grok/grok-imagine-image-2.0` | `generateImage` |
|
|
349
349
|
| `qgrid.video(id)` | `grok/grok-imagine-video`, `grok/grok-imagine-video-1.5`, `grok/grok-imagine-video-1.5-lite` | `experimental_generateVideo` |
|
|
350
|
-
| `qgrid.transcription(id)` | `grok/grok-voice-transcribe-2.0` | `transcribe` |
|
|
351
|
-
| `qgrid.speech(id)` | `grok/grok-tts`(ID는 request log 라벨로만 쓰임) | `generateSpeech` |
|
|
350
|
+
| `qgrid.transcription(id)` | `grok/grok-voice-transcribe-2.0`; OpenAI 구독: `openai/gpt-4o-mini-transcribe`, `openai/gpt-4o-transcribe`, `openai/gpt-transcribe`, `openai/gpt-live-transcribe`, `openai/gpt-realtime-whisper`, `openai/whisper-1` | `transcribe` |
|
|
351
|
+
| `qgrid.speech(id)` | `grok/grok-tts`(ID는 request log 라벨로만 쓰임); OpenAI 구독: `openai/gpt-realtime-2.1-mini`, `openai/gpt-realtime-2.1`, `openai/gpt-realtime-1.5` | `generateSpeech` |
|
|
352
352
|
|
|
353
353
|
qgrid는 카탈로그에 없는 language model ID를 거부하지 않고 그대로 전달합니다(OpenAI와 같음). 판단은
|
|
354
354
|
subscription backend가 하며 다른 모델로 응답할 수 있으므로, 실제로 실행된 모델은 request log의 응답 모델(또는
|
|
@@ -488,6 +488,35 @@ speech.audio.uint8Array;
|
|
|
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
490
|
|
|
491
|
+
### OpenAI 음성 인식과 합성 (ChatGPT 구독)
|
|
492
|
+
|
|
493
|
+
`openai/` 음성 ID는 qgrid가 Codex에 쓰는 ChatGPT 구독 토큰으로 OpenAI realtime 세션을 사용합니다. 음성 인식은 Codex의 `/realtime/calls` 흐름을, 음성 합성은 텍스트를 그대로 읽는 realtime 대화 모델을 씁니다(구독에는 `gpt-4o-mini-tts`, `tts-1`이 없습니다). 옵션은 `providerOptions.openai` 아래 `@ai-sdk/openai` 이름을 따르고, `providerOptions.qgrid.tokenName`은 `openai/` 접두사가 필요합니다.
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
const transcript = await transcribe({
|
|
497
|
+
model: qgrid.transcription("openai/gpt-4o-mini-transcribe"),
|
|
498
|
+
audio: await readFile("meeting.wav"), // PCM16 WAV 또는 raw PCM만
|
|
499
|
+
providerOptions: { openai: { language: "en", prompt: "qgrid, Codex" } },
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
const speech = await generateSpeech({
|
|
503
|
+
model: qgrid.speech("openai/gpt-realtime-2.1-mini"),
|
|
504
|
+
text: "Your build is ready.",
|
|
505
|
+
voice: "marin",
|
|
506
|
+
outputFormat: "wav",
|
|
507
|
+
providerOptions: { openai: { instructions: "Calm and brisk." } },
|
|
508
|
+
});
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
| 모델 | 그대로 전달(`@ai-sdk/openai` 이름) | 변환 | 미지원(경고) |
|
|
512
|
+
| --- | --- | --- | --- |
|
|
513
|
+
| 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
|
+
| 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` |
|
|
515
|
+
|
|
516
|
+
- 음성 인식은 먼저 Codex realtime call에서 25초 예산으로 실행됩니다(call은 생성 약 30초 뒤 정리됨). 그 안에 전사가 오지 않으면 서버가 직접 realtime 전사 소켓으로 1회 재시도합니다(최대 60초). 7 MiB 인라인 제한(24 kHz PCM 약 2.5분)은 보통 첫 경로에서 몇 초 안에 끝납니다.
|
|
517
|
+
- 음성 합성은 TTS 모델이 아니라 텍스트를 읽는 대화 모델입니다. qgrid는 `<script>` 태그 안의 텍스트만 읽고 답하지 말라고 지시합니다. 원문 그대로 읽는지는 짧은 영어 문장과 질문형 영어 캐릭터 대사에서 검증했습니다. 모델이 실제로 말한 transcript를 요청 로그에 함께 남겨 비교할 수 있지만, 한영 혼합 텍스트에서는 transcript가 일치해도 영어 음성을 알아듣기 어려운 경우가 있었습니다.
|
|
518
|
+
- 모델별 동작은 서버 저장소의 `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). 2026-11-06에는 현재 프롬프트로 `gpt-realtime-2.1`과 `gpt-realtime-2.1-mini` 모두 질문형 캐릭터 대사 12/12를 그대로 읽었습니다(이전 프롬프트에서는 `gpt-realtime-2.1`이 7/12를 답변). `gpt-realtime-1.5`는 다시 측정하지 않았습니다. 정확성이 중요하면 로그의 transcript를 입력과 비교하세요.
|
|
519
|
+
|
|
491
520
|
## Telemetry Logger
|
|
492
521
|
|
|
493
522
|
서버와 AI SDK 2.10.0부터 qgrid provider의 `generateText` 결과에서는 `result.finalStep.providerMetadata?.qgrid?.requestLogId`로 요청 로그 ID를 확인할 수 있습니다. `streamText`는 스트림 소비 후 `(await result.finalStep).providerMetadata?.qgrid?.requestLogId`로 확인합니다. 직접 API를 호출하면 `query` 응답과 스트림 `done` 이벤트의 최상위 `requestLogId`에 같은 값이 담깁니다. `logger: false` 또는 구버전 서버에서는 이 필드가 없으며, 툴 호출 후속 단계는 같은 요청 로그 ID를 공유합니다. 이 ID는 후속 요청 연결 상태가 아니며, 아래 telemetry logger를 쓰는 외부 provider에는 이 metadata가 추가되지 않습니다.
|
package/README.md
CHANGED
|
@@ -357,8 +357,8 @@ Grok model accepts the same factory config as the other models: `serverUrl` (def
|
|
|
357
357
|
| `qgrid(id)` | `grok/grok-4.7` (default catalog model), `grok/grok-4.7-build-fast`, `grok/grok-4.6`, `grok/grok-4.5`, any other `grok/<id>` | `generateText`, `streamText` |
|
|
358
358
|
| `qgrid.image(id)` | `grok/grok-imagine-image`, `grok/grok-imagine-image-2.0` | `generateImage` |
|
|
359
359
|
| `qgrid.video(id)` | `grok/grok-imagine-video`, `grok/grok-imagine-video-1.5`, `grok/grok-imagine-video-1.5-lite` | `experimental_generateVideo` |
|
|
360
|
-
| `qgrid.transcription(id)` | `grok/grok-voice-transcribe-2.0` | `transcribe` |
|
|
361
|
-
| `qgrid.speech(id)` | `grok/grok-tts` (the id only labels the request log) | `generateSpeech` |
|
|
360
|
+
| `qgrid.transcription(id)` | `grok/grok-voice-transcribe-2.0`; OpenAI subscription: `openai/gpt-4o-mini-transcribe`, `openai/gpt-4o-transcribe`, `openai/gpt-transcribe`, `openai/gpt-live-transcribe`, `openai/gpt-realtime-whisper`, `openai/whisper-1` | `transcribe` |
|
|
361
|
+
| `qgrid.speech(id)` | `grok/grok-tts` (the id only labels the request log); OpenAI subscription: `openai/gpt-realtime-2.1-mini`, `openai/gpt-realtime-2.1`, `openai/gpt-realtime-1.5` | `generateSpeech` |
|
|
362
362
|
|
|
363
363
|
qgrid forwards a language-model id that is not in its catalog unchanged instead of rejecting it,
|
|
364
364
|
as it does for OpenAI; the subscription backend decides. It may serve a different model, so check
|
|
@@ -500,6 +500,35 @@ speech.audio.uint8Array;
|
|
|
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
502
|
|
|
503
|
+
### OpenAI transcription and speech (ChatGPT subscription)
|
|
504
|
+
|
|
505
|
+
`openai/` voice ids run on OpenAI realtime sessions with the same ChatGPT subscription tokens qgrid uses for Codex: transcription through the Codex `/realtime/calls` flow, speech through a realtime conversational model that repeats the text verbatim (there is no `gpt-4o-mini-tts` or `tts-1` on subscription). Options follow `@ai-sdk/openai` under `providerOptions.openai`; `providerOptions.qgrid.tokenName` must carry the `openai/` prefix.
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
const transcript = await transcribe({
|
|
509
|
+
model: qgrid.transcription("openai/gpt-4o-mini-transcribe"),
|
|
510
|
+
audio: await readFile("meeting.wav"), // PCM16 WAV or raw PCM only
|
|
511
|
+
providerOptions: { openai: { language: "en", prompt: "qgrid, Codex" } },
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
const speech = await generateSpeech({
|
|
515
|
+
model: qgrid.speech("openai/gpt-realtime-2.1-mini"),
|
|
516
|
+
text: "Your build is ready.",
|
|
517
|
+
voice: "marin",
|
|
518
|
+
outputFormat: "wav",
|
|
519
|
+
providerOptions: { openai: { instructions: "Calm and brisk." } },
|
|
520
|
+
});
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
| Model | Faithful (`@ai-sdk/openai` names) | Transformed | Unsupported (warning) |
|
|
524
|
+
| --- | --- | --- | --- |
|
|
525
|
+
| 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
|
+
| 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` |
|
|
527
|
+
|
|
528
|
+
- 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
|
+
- 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.
|
|
530
|
+
- 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), and `gpt-realtime-1.5` often answered the text instead of repeating it (6/13). 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); `gpt-realtime-1.5` was not re-run. Compare the logged transcript with your input when fidelity matters.
|
|
531
|
+
|
|
503
532
|
## Telemetry Logger
|
|
504
533
|
|
|
505
534
|
With qgrid server and SDK 2.10.0+, read the parent request-log ID from `result.finalStep.providerMetadata?.qgrid?.requestLogId` after `generateText`. For `streamText`, consume the stream first, then read `(await result.finalStep).providerMetadata?.qgrid?.requestLogId`. Raw API query responses and stream `done` events expose the same ID as top-level `requestLogId`. The field is omitted with `logger: false` or an older server. Tool-run steps share the parent ID; it is not continuation state. This metadata applies to the qgrid provider, not external providers using the telemetry logger below.
|
package/dist/index.d.ts
CHANGED
|
@@ -40,12 +40,28 @@ type QgridImageModel = "openai/gpt-image-2" | "grok/grok-imagine-image" | "grok/
|
|
|
40
40
|
/** Grok video generation model ids (xAI subscription; not yet wired to a provider factory). */
|
|
41
41
|
type QgridVideoModel = "grok/grok-imagine-video" | "grok/grok-imagine-video-1.5" | "grok/grok-imagine-video-1.5-lite";
|
|
42
42
|
/** Grok speech-to-text model ids. */
|
|
43
|
-
type
|
|
43
|
+
type QgridGrokTranscriptionModel = "grok/grok-voice-transcribe-2.0";
|
|
44
|
+
/**
|
|
45
|
+
* OpenAI subscription speech-to-text ids, served through a realtime transcription session.
|
|
46
|
+
* Known limits (2026-10-17 live matrix): `gpt-live-transcribe` and `gpt-realtime-whisper`
|
|
47
|
+
* returned about half of a 60 s clip; `gpt-realtime-whisper` rejects `openai.prompt` (HTTP 400
|
|
48
|
+
* before any upstream call); `whisper-1` accepts `openai.prompt` without error but the upstream
|
|
49
|
+
* ignores it, so it cannot steer spelling.
|
|
50
|
+
*/
|
|
51
|
+
type QgridOpenAITranscriptionModel = "openai/gpt-4o-mini-transcribe" | "openai/gpt-4o-transcribe" | "openai/gpt-transcribe" | "openai/gpt-live-transcribe" | "openai/gpt-realtime-whisper" | "openai/whisper-1";
|
|
52
|
+
type QgridTranscriptionModel = QgridGrokTranscriptionModel | QgridOpenAITranscriptionModel;
|
|
44
53
|
/**
|
|
45
54
|
* Grok text-to-speech model id. Placeholder: the live probe verified the `/tts` endpoint with the
|
|
46
55
|
* subscription JWT, but no TTS model id was observed (`/speech-models` returned 404).
|
|
47
56
|
*/
|
|
48
|
-
type
|
|
57
|
+
type QgridGrokSpeechModel = "grok/grok-tts";
|
|
58
|
+
/**
|
|
59
|
+
* OpenAI subscription text-to-speech ids: realtime conversational models repeating the text
|
|
60
|
+
* (`gpt-4o-mini-tts`/`tts-1` are unavailable on subscription). Verbatim fidelity on 2026-10-17:
|
|
61
|
+
* `gpt-realtime-2.1-mini` 13/13, `gpt-realtime-2.1` 12/13, `gpt-realtime-1.5` 6/13.
|
|
62
|
+
*/
|
|
63
|
+
type QgridOpenAISpeechModel = "openai/gpt-realtime-2.1-mini" | "openai/gpt-realtime-2.1" | "openai/gpt-realtime-1.5";
|
|
64
|
+
type QgridSpeechModel = QgridGrokSpeechModel | QgridOpenAISpeechModel;
|
|
49
65
|
type QgridImageProviderConfig = QgridCommonProviderConfig;
|
|
50
66
|
/** `providerOptions.qgrid` for `generateImage({ model: qgrid.image(...) })`. */
|
|
51
67
|
type QgridImageProviderOptions = {
|
|
@@ -219,6 +235,12 @@ type QgridLoggerConfig = {
|
|
|
219
235
|
export declare const QGRID_MAX_REQUEST_BODY_BYTES: number;
|
|
220
236
|
/** `config` for `qgrid.image/video/transcription/speech`; same fields as the language model. */
|
|
221
237
|
type QgridMediaProviderConfig = QgridImageProviderConfig;
|
|
238
|
+
declare const openaiQgridOptionsSchema: z.ZodObject<{
|
|
239
|
+
tokenName: z.ZodOptional<z.ZodString>;
|
|
240
|
+
logger: z.ZodOptional<z.ZodBoolean>;
|
|
241
|
+
}, z.core.$strip>;
|
|
242
|
+
/** `providerOptions.qgrid` accepted by OpenAI voice models. */
|
|
243
|
+
type QgridOpenAIMediaProviderOptions = z.infer<typeof openaiQgridOptionsSchema>;
|
|
222
244
|
/** Non-2xx response from a qgrid media endpoint. */
|
|
223
245
|
export declare class QgridMediaHttpError extends Error {
|
|
224
246
|
readonly status: number;
|
|
@@ -253,6 +275,10 @@ declare const xaiImageOptionsSchema: z.ZodObject<{
|
|
|
253
275
|
type QgridXaiImageProviderOptions = z.infer<typeof xaiImageOptionsSchema>;
|
|
254
276
|
//#endregion
|
|
255
277
|
//#region src/speech-model.d.ts
|
|
278
|
+
/** `providerOptions.openai` honored by `qgrid.speech("openai/...")`. */
|
|
279
|
+
type QgridOpenAISpeechProviderOptions = {
|
|
280
|
+
instructions?: string;
|
|
281
|
+
};
|
|
256
282
|
/**
|
|
257
283
|
* Grok text-to-speech through `POST /api/qgrid/speak`. `voice`, `language` and `outputFormat`
|
|
258
284
|
* map to the server's `voice`, `language` and `format`; when omitted the server applies
|
|
@@ -265,6 +291,18 @@ declare function createQgridSpeech(modelId: QgridSpeechModel, config?: QgridMedi
|
|
|
265
291
|
type QgridXaiTranscriptionProviderOptions = {
|
|
266
292
|
language?: string;
|
|
267
293
|
};
|
|
294
|
+
/**
|
|
295
|
+
* `providerOptions.openai` honored by `qgrid.transcription("openai/...")`.
|
|
296
|
+
*
|
|
297
|
+
* `prompt` steers vocabulary on `gpt-4o-mini-transcribe`, `gpt-4o-transcribe`, `gpt-transcribe`
|
|
298
|
+
* and `gpt-live-transcribe`. `openai/gpt-realtime-whisper` rejects it (the server answers HTTP
|
|
299
|
+
* 400 before any upstream call). `openai/whisper-1` accepts it but the upstream ignores it
|
|
300
|
+
* (verified 2026-10-17: "qgrid" still came back as "Thread"), so do not rely on it for spelling.
|
|
301
|
+
*/
|
|
302
|
+
type QgridOpenAITranscriptionProviderOptions = {
|
|
303
|
+
language?: string;
|
|
304
|
+
prompt?: string;
|
|
305
|
+
};
|
|
268
306
|
declare function createQgridTranscription(modelId: QgridTranscriptionModel, config?: QgridMediaProviderConfig): TranscriptionModelV3;
|
|
269
307
|
//#endregion
|
|
270
308
|
//#region src/video-model.d.ts
|
|
@@ -337,4 +375,4 @@ export declare namespace qgrid {
|
|
|
337
375
|
var speech: typeof createQgridSpeech;
|
|
338
376
|
}
|
|
339
377
|
//#endregion
|
|
340
|
-
export { type QgridAnthropicEffort, type QgridAnthropicModel, type QgridAnthropicProviderConfig, type QgridAnthropicProviderOptions, type QgridGrokEffort, type QgridGrokMediaProviderOptions, type QgridGrokModel, type QgridImageGenerationMetadata, type QgridImageModel, type QgridImageProviderConfig, type QgridImageProviderOptions, type QgridLoggerConfig, type QgridMediaProviderConfig, type QgridOpenAIEffort, type QgridOpenAIModel, type QgridOpenAIProviderConfig, type QgridOpenAIProviderOptions, type QgridProviderConfig, type QgridProviderOptions, type QgridSpeechModel, type QgridSupportedModel, type QgridTranscriptionModel, type QgridVideoModel, type QgridVideoPollOptions, type QgridVideoResult, type QgridXaiImageProviderOptions, type QgridXaiProviderOptions, type QgridXaiTranscriptionProviderOptions, type QgridXaiVideoProviderOptions, qgrid as default };
|
|
378
|
+
export { type QgridAnthropicEffort, type QgridAnthropicModel, type QgridAnthropicProviderConfig, type QgridAnthropicProviderOptions, type QgridGrokEffort, type QgridGrokMediaProviderOptions, type QgridGrokModel, type QgridGrokSpeechModel, type QgridGrokTranscriptionModel, type QgridImageGenerationMetadata, type QgridImageModel, type QgridImageProviderConfig, type QgridImageProviderOptions, type QgridLoggerConfig, type QgridMediaProviderConfig, type QgridOpenAIEffort, type QgridOpenAIMediaProviderOptions, type QgridOpenAIModel, type QgridOpenAIProviderConfig, type QgridOpenAIProviderOptions, type QgridOpenAISpeechModel, type QgridOpenAISpeechProviderOptions, type QgridOpenAITranscriptionModel, type QgridOpenAITranscriptionProviderOptions, type QgridProviderConfig, type QgridProviderOptions, type QgridSpeechModel, type QgridSupportedModel, type QgridTranscriptionModel, type QgridVideoModel, type QgridVideoPollOptions, type QgridVideoResult, type QgridXaiImageProviderOptions, type QgridXaiProviderOptions, type QgridXaiTranscriptionProviderOptions, type QgridXaiVideoProviderOptions, qgrid as default };
|
package/dist/index.js
CHANGED
|
@@ -1000,6 +1000,27 @@ const GROK_MODEL_ID = /^grok\/[A-Za-z0-9._-]+$/;
|
|
|
1000
1000
|
function assertGrokModelId(modelId, label) {
|
|
1001
1001
|
if (!GROK_MODEL_ID.test(modelId)) throw new Error(`${label}: unsupported model ${modelId}; expected grok/<id>`);
|
|
1002
1002
|
}
|
|
1003
|
+
/** Voice factories accept `grok/<id>` and `openai/<id>`; everything else throws at creation. */
|
|
1004
|
+
function voiceProviderOf(modelId, label) {
|
|
1005
|
+
if (GROK_MODEL_ID.test(modelId)) return "grok";
|
|
1006
|
+
if (/^openai\/[A-Za-z0-9._-]+$/.test(modelId)) return "openai";
|
|
1007
|
+
throw new Error(`${label}: unsupported model ${modelId}; expected grok/<id> or openai/<id>`);
|
|
1008
|
+
}
|
|
1009
|
+
const openaiQgridOptionsSchema = z.object({
|
|
1010
|
+
tokenName: z.string().refine((value) => /^openai\/.+/.test(value.trim()), { message: "tokenName must be a non-empty OpenAI token name with the openai/ prefix" }).optional(),
|
|
1011
|
+
logger: z.boolean({ message: "logger must be a boolean" }).optional()
|
|
1012
|
+
});
|
|
1013
|
+
/** `providerOptions.qgrid` for an `openai/` voice model: tokenName must carry the openai/ prefix. */
|
|
1014
|
+
function parseOpenAIQgridOptions(raw, label) {
|
|
1015
|
+
if (raw === void 0) return {};
|
|
1016
|
+
if (!providerOptionsObjectSchema.safeParse(raw).success) throw new Error(`${label}: providerOptions.qgrid must be an object`);
|
|
1017
|
+
const parsed = openaiQgridOptionsSchema.safeParse(raw);
|
|
1018
|
+
if (!parsed.success) throw new Error(`${label}: ${parsed.error.issues.map((issue) => issue.message).join("; ")}`);
|
|
1019
|
+
const request = {};
|
|
1020
|
+
if (parsed.data.tokenName !== void 0) request.tokenName = parsed.data.tokenName;
|
|
1021
|
+
if (parsed.data.logger === false) request.logger = false;
|
|
1022
|
+
return request;
|
|
1023
|
+
}
|
|
1003
1024
|
function formatMiB(bytes) {
|
|
1004
1025
|
return `${(bytes / 1024 / 1024).toFixed(2)} MiB`;
|
|
1005
1026
|
}
|
|
@@ -1118,21 +1139,29 @@ function parseGrokQgridOptions(raw, label) {
|
|
|
1118
1139
|
* `honored`. Malformed values throw; documented-but-unsupported keys become warnings.
|
|
1119
1140
|
*/
|
|
1120
1141
|
function parseXaiMediaOptions(raw, schema, honored, label) {
|
|
1142
|
+
return parseNamespacedMediaOptions("xai", raw, schema, honored, label);
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Validates `providerOptions.<namespace>` against the official provider's schema and warns for
|
|
1146
|
+
* every set key qgrid does not honor, so code written for `@ai-sdk/xai` or `@ai-sdk/openai`
|
|
1147
|
+
* keeps parsing and learns what was dropped.
|
|
1148
|
+
*/
|
|
1149
|
+
function parseNamespacedMediaOptions(namespace, raw, schema, honored, label) {
|
|
1121
1150
|
if (raw === void 0) return {
|
|
1122
1151
|
options: void 0,
|
|
1123
1152
|
warnings: []
|
|
1124
1153
|
};
|
|
1125
1154
|
const record = providerOptionsObjectSchema.safeParse(raw);
|
|
1126
|
-
if (!record.success) throw new Error(`${label}: providerOptions
|
|
1155
|
+
if (!record.success) throw new Error(`${label}: providerOptions.${namespace} must be an object`);
|
|
1127
1156
|
const parsed = schema.safeParse(raw);
|
|
1128
|
-
if (!parsed.success) throw new Error(`${label}: invalid providerOptions
|
|
1157
|
+
if (!parsed.success) throw new Error(`${label}: invalid providerOptions.${namespace}: ${formatIssues(parsed.error)}`);
|
|
1129
1158
|
const warnings = [];
|
|
1130
1159
|
for (const [key, value] of Object.entries(record.data)) {
|
|
1131
1160
|
if (value === void 0 || value === null || honored.has(key)) continue;
|
|
1132
1161
|
warnings.push({
|
|
1133
1162
|
type: "unsupported",
|
|
1134
|
-
feature: `providerOptions
|
|
1135
|
-
details: `qgrid does not support providerOptions
|
|
1163
|
+
feature: `providerOptions.${namespace}.${key}`,
|
|
1164
|
+
details: `qgrid does not support providerOptions.${namespace}.${key} for this model; it was ignored.`
|
|
1136
1165
|
});
|
|
1137
1166
|
}
|
|
1138
1167
|
return {
|
|
@@ -1476,6 +1505,21 @@ const xaiSpeechOptionsSchema = z.object({
|
|
|
1476
1505
|
withTimestamps: z.boolean().nullish(),
|
|
1477
1506
|
replace: z.record(z.string(), z.string()).nullish()
|
|
1478
1507
|
});
|
|
1508
|
+
/**
|
|
1509
|
+
* `providerOptions.openai` for OpenAI speech, field-for-field from `@ai-sdk/openai`
|
|
1510
|
+
* `openaiSpeechModelOptionsSchema` (vercel/ai fce9210). `instructions` is honored as style
|
|
1511
|
+
* guidance for the realtime model; `speed` warns.
|
|
1512
|
+
*/
|
|
1513
|
+
const openaiSpeechOptionsSchema = z.object({
|
|
1514
|
+
instructions: z.string().nullish(),
|
|
1515
|
+
speed: z.number().min(.25).max(4).nullish()
|
|
1516
|
+
});
|
|
1517
|
+
const OPENAI_SPEECH_HONORED = /* @__PURE__ */ new Set(["instructions"]);
|
|
1518
|
+
/** Realtime speech output: PCM16 24 kHz, as WAV (default) or raw PCM. */
|
|
1519
|
+
const OPENAI_SPEECH_FORMATS = ["wav", "pcm"];
|
|
1520
|
+
function isOpenAISpeechFormat(value) {
|
|
1521
|
+
return OPENAI_SPEECH_FORMATS.some((format) => format === value);
|
|
1522
|
+
}
|
|
1479
1523
|
const speakOutputSchema = z.object({
|
|
1480
1524
|
audio: z.string(),
|
|
1481
1525
|
mimeType: z.string(),
|
|
@@ -1495,8 +1539,9 @@ function base64ToBytes(data) {
|
|
|
1495
1539
|
* `ara` / `en` / `mp3` (the only combination verified live). The model id only labels the log.
|
|
1496
1540
|
*/
|
|
1497
1541
|
function createQgridSpeech(modelId, config) {
|
|
1498
|
-
|
|
1542
|
+
const provider = voiceProviderOf(modelId, LABEL$2);
|
|
1499
1543
|
const { serverUrl, projectName } = resolveMediaConfig(config);
|
|
1544
|
+
if (provider === "openai") return createOpenAISpeech(modelId, serverUrl, projectName);
|
|
1500
1545
|
return {
|
|
1501
1546
|
specificationVersion: "v3",
|
|
1502
1547
|
provider: QGRID_PROVIDER_NAME,
|
|
@@ -1570,6 +1615,89 @@ function createQgridSpeech(modelId, config) {
|
|
|
1570
1615
|
}
|
|
1571
1616
|
};
|
|
1572
1617
|
}
|
|
1618
|
+
/**
|
|
1619
|
+
* OpenAI subscription speech: a realtime conversational model repeating the text verbatim (there
|
|
1620
|
+
* is no `gpt-4o-mini-tts` on subscription). Follows `@ai-sdk/openai` option names: `voice`
|
|
1621
|
+
* (default `marin`, Codex v2 list), `outputFormat` `wav` (default) or `pcm` — other formats
|
|
1622
|
+
* warn and fall back to wav, as the official provider falls back to mp3 — `openai.instructions`
|
|
1623
|
+
* as style guidance, `openai.speed` and `language` unsupported with the official warning text.
|
|
1624
|
+
*/
|
|
1625
|
+
function createOpenAISpeech(modelId, serverUrl, projectName) {
|
|
1626
|
+
return {
|
|
1627
|
+
specificationVersion: "v3",
|
|
1628
|
+
provider: QGRID_PROVIDER_NAME,
|
|
1629
|
+
modelId,
|
|
1630
|
+
async doGenerate(options) {
|
|
1631
|
+
if (!options.text.trim()) throw new Error(`${LABEL$2}: text must not be empty`);
|
|
1632
|
+
if (options.text.length > MAX_TEXT_CHARS) throw new Error(`${LABEL$2}: text is limited to ${MAX_TEXT_CHARS} characters`);
|
|
1633
|
+
const qgridOptions = parseOpenAIQgridOptions(options.providerOptions?.qgrid, LABEL$2);
|
|
1634
|
+
const openai = parseNamespacedMediaOptions("openai", options.providerOptions?.openai, openaiSpeechOptionsSchema, OPENAI_SPEECH_HONORED, LABEL$2);
|
|
1635
|
+
const warnings = [];
|
|
1636
|
+
let format;
|
|
1637
|
+
if (options.outputFormat !== void 0) {
|
|
1638
|
+
if (isOpenAISpeechFormat(options.outputFormat)) format = options.outputFormat;
|
|
1639
|
+
else {
|
|
1640
|
+
format = "wav";
|
|
1641
|
+
warnings.push({
|
|
1642
|
+
type: "unsupported",
|
|
1643
|
+
feature: "outputFormat",
|
|
1644
|
+
details: `Unsupported output format: ${options.outputFormat}. Using wav instead.`
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
if (options.language !== void 0) warnings.push({
|
|
1649
|
+
type: "unsupported",
|
|
1650
|
+
feature: "language",
|
|
1651
|
+
details: `OpenAI speech models do not support language selection. Language parameter "${options.language}" was ignored.`
|
|
1652
|
+
});
|
|
1653
|
+
if (options.speed !== void 0) warnings.push({
|
|
1654
|
+
type: "unsupported",
|
|
1655
|
+
feature: "speed"
|
|
1656
|
+
});
|
|
1657
|
+
warnings.push(...openai.warnings);
|
|
1658
|
+
const requestParams = {
|
|
1659
|
+
model: modelId,
|
|
1660
|
+
text: options.text
|
|
1661
|
+
};
|
|
1662
|
+
if (options.voice !== void 0) requestParams.voice = options.voice;
|
|
1663
|
+
if (format !== void 0) requestParams.format = format;
|
|
1664
|
+
const instructions = options.instructions ?? openai.options?.instructions ?? void 0;
|
|
1665
|
+
if (instructions !== void 0) requestParams.instructions = instructions;
|
|
1666
|
+
if (projectName) requestParams.projectName = projectName;
|
|
1667
|
+
const params = {
|
|
1668
|
+
...requestParams,
|
|
1669
|
+
...qgridOptions
|
|
1670
|
+
};
|
|
1671
|
+
const body = serializeMediaBody(params, LABEL$2);
|
|
1672
|
+
const timestamp = /* @__PURE__ */ new Date();
|
|
1673
|
+
const { data, headers } = await postMediaEndpoint({
|
|
1674
|
+
serverUrl,
|
|
1675
|
+
endpoint: "speak",
|
|
1676
|
+
body,
|
|
1677
|
+
schema: speakOutputSchema,
|
|
1678
|
+
label: LABEL$2,
|
|
1679
|
+
signal: options.abortSignal,
|
|
1680
|
+
headers: options.headers
|
|
1681
|
+
});
|
|
1682
|
+
const qgridMetadata = {
|
|
1683
|
+
mediaType: data.mimeType,
|
|
1684
|
+
tokenName: data.tokenName
|
|
1685
|
+
};
|
|
1686
|
+
if (data.requestLogId !== void 0) qgridMetadata.requestLogId = data.requestLogId;
|
|
1687
|
+
return {
|
|
1688
|
+
audio: base64ToBytes(data.audio),
|
|
1689
|
+
warnings,
|
|
1690
|
+
request: { body: params },
|
|
1691
|
+
response: {
|
|
1692
|
+
timestamp,
|
|
1693
|
+
modelId,
|
|
1694
|
+
headers
|
|
1695
|
+
},
|
|
1696
|
+
providerMetadata: { qgrid: qgridMetadata }
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1573
1701
|
//#endregion
|
|
1574
1702
|
//#region src/transcription-model.ts
|
|
1575
1703
|
const LABEL$1 = "qgrid.transcription";
|
|
@@ -1604,6 +1732,40 @@ const xaiTranscriptionOptionsSchema = z.object({
|
|
|
1604
1732
|
streaming: z.object({}).loose().optional()
|
|
1605
1733
|
});
|
|
1606
1734
|
const XAI_TRANSCRIPTION_HONORED = /* @__PURE__ */ new Set(["language"]);
|
|
1735
|
+
/**
|
|
1736
|
+
* `providerOptions.openai` for OpenAI transcription, field-for-field from `@ai-sdk/openai`
|
|
1737
|
+
* `openAITranscriptionModelOptions` (vercel/ai fce9210). The realtime transcription session
|
|
1738
|
+
* honors `language` and `prompt`; the rest warn.
|
|
1739
|
+
*/
|
|
1740
|
+
const openaiTranscriptionOptionsSchema = z.object({
|
|
1741
|
+
include: z.array(z.string()).optional(),
|
|
1742
|
+
language: z.string().optional(),
|
|
1743
|
+
prompt: z.string().optional(),
|
|
1744
|
+
temperature: z.number().min(0).max(1).optional(),
|
|
1745
|
+
timestampGranularities: z.array(z.enum(["word", "segment"])).optional(),
|
|
1746
|
+
responseFormat: z.enum([
|
|
1747
|
+
"json",
|
|
1748
|
+
"verbose_json",
|
|
1749
|
+
"diarized_json"
|
|
1750
|
+
]).optional(),
|
|
1751
|
+
chunkingStrategy: z.union([z.literal("auto"), z.object({
|
|
1752
|
+
type: z.literal("server_vad"),
|
|
1753
|
+
threshold: z.number().min(0).max(1).optional(),
|
|
1754
|
+
prefixPaddingMs: z.number().int().min(0).optional(),
|
|
1755
|
+
silenceDurationMs: z.number().int().min(0).optional()
|
|
1756
|
+
})]).optional(),
|
|
1757
|
+
streaming: z.object({
|
|
1758
|
+
delay: z.enum([
|
|
1759
|
+
"minimal",
|
|
1760
|
+
"low",
|
|
1761
|
+
"medium",
|
|
1762
|
+
"high",
|
|
1763
|
+
"xhigh"
|
|
1764
|
+
]).optional(),
|
|
1765
|
+
include: z.array(z.string()).optional()
|
|
1766
|
+
}).optional()
|
|
1767
|
+
});
|
|
1768
|
+
const OPENAI_TRANSCRIPTION_HONORED = /* @__PURE__ */ new Set(["language", "prompt"]);
|
|
1607
1769
|
const transcribeOutputSchema = z.object({
|
|
1608
1770
|
text: z.string(),
|
|
1609
1771
|
language: z.string().optional(),
|
|
@@ -1618,7 +1780,7 @@ function normalizeMediaType(mediaType) {
|
|
|
1618
1780
|
return bare;
|
|
1619
1781
|
}
|
|
1620
1782
|
function createQgridTranscription(modelId, config) {
|
|
1621
|
-
|
|
1783
|
+
const provider = voiceProviderOf(modelId, LABEL$1);
|
|
1622
1784
|
const { serverUrl, projectName } = resolveMediaConfig(config);
|
|
1623
1785
|
return {
|
|
1624
1786
|
specificationVersion: "v3",
|
|
@@ -1629,16 +1791,24 @@ function createQgridTranscription(modelId, config) {
|
|
|
1629
1791
|
const audio = options.audio instanceof Uint8Array ? bytesToBase64(options.audio) : options.audio;
|
|
1630
1792
|
if (audio.length === 0) throw new Error(`${LABEL$1}: audio is empty`);
|
|
1631
1793
|
assertInlineMediaFits([audio], LABEL$1);
|
|
1632
|
-
const qgridOptions = parseGrokQgridOptions(options.providerOptions?.qgrid, LABEL$1);
|
|
1633
|
-
const
|
|
1634
|
-
const warnings = [...xai.warnings];
|
|
1635
|
-
const language = xai.options?.language ?? void 0;
|
|
1794
|
+
const qgridOptions = provider === "openai" ? parseOpenAIQgridOptions(options.providerOptions?.qgrid, LABEL$1) : parseGrokQgridOptions(options.providerOptions?.qgrid, LABEL$1);
|
|
1795
|
+
const warnings = [];
|
|
1636
1796
|
const params = {
|
|
1637
1797
|
model: modelId,
|
|
1638
1798
|
audio,
|
|
1639
1799
|
mediaType
|
|
1640
1800
|
};
|
|
1641
|
-
if (
|
|
1801
|
+
if (provider === "openai") {
|
|
1802
|
+
const openai = parseNamespacedMediaOptions("openai", options.providerOptions?.openai, openaiTranscriptionOptionsSchema, OPENAI_TRANSCRIPTION_HONORED, LABEL$1);
|
|
1803
|
+
warnings.push(...openai.warnings);
|
|
1804
|
+
if (openai.options?.language !== void 0) params.language = openai.options.language;
|
|
1805
|
+
if (openai.options?.prompt !== void 0) params.prompt = openai.options.prompt;
|
|
1806
|
+
} else {
|
|
1807
|
+
const xai = parseXaiMediaOptions(options.providerOptions?.xai, xaiTranscriptionOptionsSchema, XAI_TRANSCRIPTION_HONORED, LABEL$1);
|
|
1808
|
+
warnings.push(...xai.warnings);
|
|
1809
|
+
const language = xai.options?.language ?? void 0;
|
|
1810
|
+
if (language !== void 0) params.language = language;
|
|
1811
|
+
}
|
|
1642
1812
|
if (projectName) params.projectName = projectName;
|
|
1643
1813
|
const body = serializeMediaBody({
|
|
1644
1814
|
...params,
|