@cartanova/qgrid-ai-sdk 2.10.10 → 2.11.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/dist/index.js CHANGED
@@ -1,15 +1,16 @@
1
+ import { z } from "zod";
1
2
  import { Agent } from "undici";
2
3
  //#region src/envelope-stream-parser.ts
3
- const ESCAPE_MAP = {
4
- "\"": "\"",
5
- "\\": "\\",
6
- "/": "/",
7
- b: "\b",
8
- f: "\f",
9
- n: "\n",
10
- r: "\r",
11
- t: " "
12
- };
4
+ const ESCAPE_MAP = /* @__PURE__ */ new Map([
5
+ ["\"", "\""],
6
+ ["\\", "\\"],
7
+ ["/", "/"],
8
+ ["b", "\b"],
9
+ ["f", "\f"],
10
+ ["n", "\n"],
11
+ ["r", "\r"],
12
+ ["t", " "]
13
+ ]);
13
14
  const LITERAL_CHARS = /[0-9a-zA-Z+\-.eE]/;
14
15
  function createEnvelopeStreamParser(answerKind) {
15
16
  let action = null;
@@ -179,7 +180,7 @@ function createEnvelopeStreamParser(answerKind) {
179
180
  unicode = "";
180
181
  return;
181
182
  }
182
- const mapped = ESCAPE_MAP[c];
183
+ const mapped = ESCAPE_MAP.get(c);
183
184
  if (mapped === void 0) {
184
185
  discard = true;
185
186
  return;
@@ -253,9 +254,300 @@ function createEnvelopeStreamParser(answerKind) {
253
254
  } };
254
255
  }
255
256
  //#endregion
257
+ //#region src/index.types.ts
258
+ /**
259
+ * `providerOptions.xai` for Grok models, cloned field-for-field from `@ai-sdk/xai`
260
+ * (`xai-responses-language-model-options.ts`) so code written for `@ai-sdk/xai` keeps parsing.
261
+ * Parsing only proves the shape; `mapXaiProviderOptions` decides which keys qgrid honors.
262
+ */
263
+ const qgridXaiProviderOptionsSchema = z.object({
264
+ /**
265
+ * Constrains how hard a reasoning model thinks before responding. qgrid validates the value
266
+ * against the model's catalog effort set; `none` is rejected because the proxy always reasons.
267
+ */
268
+ reasoningEffort: z.enum([
269
+ "none",
270
+ "low",
271
+ "medium",
272
+ "high",
273
+ "xhigh"
274
+ ]).optional(),
275
+ /** Maximum number of agentic tool-calling turns. Rejected by qgrid. */
276
+ maxTurns: z.number().int().optional(),
277
+ /** Whether the model may call tools in parallel. @default true */
278
+ parallelToolCalls: z.boolean().optional(),
279
+ /** Cache key used to route requests with shared prompt prefixes. */
280
+ promptCacheKey: z.string().optional(),
281
+ /** Stable identifier used to attribute policy violations to an end user. Rejected by qgrid. */
282
+ safetyIdentifier: z.string().optional(),
283
+ serviceTier: z.enum(["default", "priority"]).optional(),
284
+ /** Rejected by qgrid: it always sends `store: false`. */
285
+ store: z.boolean().optional(),
286
+ /** Rejected by qgrid: it replays history itself. */
287
+ previousResponseId: z.string().optional(),
288
+ /** A unique identifier for the end user, used for abuse monitoring. Rejected by qgrid. */
289
+ user: z.string().optional(),
290
+ /** Rejected by qgrid: it fixes `include` itself. */
291
+ include: z.array(z.enum([
292
+ "file_search_call.results",
293
+ "web_search_call.action.sources",
294
+ "code_interpreter_call.outputs",
295
+ "reasoning.encrypted_content",
296
+ "no_inline_citations"
297
+ ])).nullish()
298
+ });
299
+ const imageGenerationMetadataSchema = z.looseObject({
300
+ route: z.enum(["codex-images", "codex-responses"]),
301
+ model: z.string().optional(),
302
+ background: z.string().optional(),
303
+ quality: z.string().optional(),
304
+ size: z.string().optional(),
305
+ usage: z.looseObject({
306
+ input_tokens: z.number(),
307
+ output_tokens: z.number(),
308
+ total_tokens: z.number(),
309
+ input_tokens_details: z.looseObject({
310
+ image_tokens: z.number(),
311
+ text_tokens: z.number(),
312
+ cached_tokens: z.number().optional()
313
+ }).optional(),
314
+ output_tokens_details: z.looseObject({
315
+ image_tokens: z.number(),
316
+ text_tokens: z.number()
317
+ }).optional()
318
+ }).optional()
319
+ });
320
+ const queryContentPartSchema = z.discriminatedUnion("type", [
321
+ z.object({
322
+ type: z.literal("text"),
323
+ text: z.string()
324
+ }),
325
+ z.object({
326
+ type: z.literal("tool-call"),
327
+ toolCallId: z.string(),
328
+ toolName: z.string(),
329
+ input: z.string()
330
+ }),
331
+ z.object({
332
+ type: z.literal("image"),
333
+ data: z.string(),
334
+ mediaType: z.enum([
335
+ "image/png",
336
+ "image/jpeg",
337
+ "image/webp"
338
+ ]).optional(),
339
+ revisedPrompt: z.string().nullable().optional(),
340
+ generation: imageGenerationMetadataSchema.optional()
341
+ })
342
+ ]);
343
+ const threadCoordSchema = z.looseObject({
344
+ workerId: z.number(),
345
+ threadId: z.string(),
346
+ epoch: z.number(),
347
+ systemHash: z.string().optional()
348
+ });
349
+ /**
350
+ * Response of `/api/qgrid/query` and the stream `done` event. Content variants this SDK does not
351
+ * know are skipped rather than rejected, so a newer server can add variants. The object stays
352
+ * loose because `providerMetadata` is read by a separate schema.
353
+ */
354
+ const queryOutputSchema = z.looseObject({
355
+ requestLogId: z.number().optional(),
356
+ text: z.string(),
357
+ content: z.array(z.looseObject({ type: z.string() })).optional().transform((items) => items?.flatMap((item) => {
358
+ const part = queryContentPartSchema.safeParse(item);
359
+ return part.success ? [part.data] : [];
360
+ })),
361
+ finishReason: z.enum(["stop", "tool-calls"]).optional(),
362
+ model: z.string().optional(),
363
+ requestedModel: z.string().optional(),
364
+ modelFallbacks: z.array(z.object({
365
+ trigger: z.literal("refusal"),
366
+ fromModel: z.string(),
367
+ toModel: z.string(),
368
+ category: z.string().optional(),
369
+ explanation: z.string().optional()
370
+ })).optional(),
371
+ tokenName: z.string().optional(),
372
+ usage: z.object({
373
+ input_tokens: z.number(),
374
+ output_tokens: z.number(),
375
+ reasoning_tokens: z.number().optional(),
376
+ cache_creation_input_tokens: z.number(),
377
+ cache_creation_5m_input_tokens: z.number().optional(),
378
+ cache_creation_1h_input_tokens: z.number().optional(),
379
+ cache_read_input_tokens: z.number()
380
+ }),
381
+ durationMs: z.number().optional(),
382
+ costUsd: z.number().optional(),
383
+ costSource: z.enum([
384
+ "provider",
385
+ "pricing_table",
386
+ "mixed"
387
+ ]).optional(),
388
+ runContext: z.object({
389
+ requestLogId: z.number().optional(),
390
+ threadCoord: threadCoordSchema.optional()
391
+ }).optional()
392
+ });
393
+ //#endregion
394
+ //#region src/grok-options.ts
395
+ /**
396
+ * How each documented `providerOptions.xai` key reaches the qgrid request.
397
+ * faithful: same meaning, same name. transformed: renamed and validated. rejected: dropped with a warning.
398
+ */
399
+ const XAI_PROVIDER_OPTION_CLASSIFICATION = {
400
+ reasoningEffort: {
401
+ kind: "transformed",
402
+ target: "effort"
403
+ },
404
+ promptCacheKey: {
405
+ kind: "faithful",
406
+ target: "promptCacheKey"
407
+ },
408
+ serviceTier: {
409
+ kind: "rejected",
410
+ reason: "the Grok subscription proxy's handling of serviceTier is unverified"
411
+ },
412
+ parallelToolCalls: {
413
+ kind: "rejected",
414
+ reason: "the Grok subscription proxy's handling of parallelToolCalls is unverified"
415
+ },
416
+ store: {
417
+ kind: "rejected",
418
+ reason: "qgrid always sends store: false"
419
+ },
420
+ previousResponseId: {
421
+ kind: "rejected",
422
+ reason: "qgrid replays conversation history itself"
423
+ },
424
+ include: {
425
+ kind: "rejected",
426
+ reason: "qgrid fixes the include list itself"
427
+ },
428
+ maxTurns: {
429
+ kind: "rejected",
430
+ reason: "qgrid does not run server-side agentic turns"
431
+ },
432
+ user: {
433
+ kind: "rejected",
434
+ reason: "qgrid does not forward end-user identifiers"
435
+ },
436
+ safetyIdentifier: {
437
+ kind: "rejected",
438
+ reason: "qgrid does not forward end-user identifiers"
439
+ }
440
+ };
441
+ /** Catalog effort set (`/models` `reasoning_efforts`) observed at GROK_BUILD_VERSION 1.0.60. */
442
+ const DEFAULT_GROK_EFFORTS = [
443
+ "xhigh",
444
+ "high",
445
+ "medium",
446
+ "low"
447
+ ];
448
+ /** Per-model overrides of `DEFAULT_GROK_EFFORTS`, keyed by the backend id without the `grok/` prefix. */
449
+ const GROK_MODEL_EFFORTS = /* @__PURE__ */ new Map([
450
+ ["grok-4.7", DEFAULT_GROK_EFFORTS],
451
+ ["grok-4.7-build-fast", DEFAULT_GROK_EFFORTS],
452
+ ["grok-4.6", DEFAULT_GROK_EFFORTS],
453
+ ["grok-4.5", DEFAULT_GROK_EFFORTS]
454
+ ]);
455
+ /** Thrown when `providerOptions.xai` has an invalid shape or value type. */
456
+ var QgridXaiOptionsError = class extends Error {
457
+ issues;
458
+ constructor(issues) {
459
+ const detail = issues.map((issue) => `${issue.path || "(root)"}: ${issue.message}`).join("; ");
460
+ super(`qgrid: invalid providerOptions.xai: ${detail}`);
461
+ this.name = "QgridXaiOptionsError";
462
+ this.issues = issues;
463
+ }
464
+ };
465
+ /** Object.getPrototypeOf boxes primitives, so only plain and null-prototype objects pass. */
466
+ function isPlainJsonObject(value) {
467
+ if (value === null || Array.isArray(value)) return false;
468
+ const proto = Object.getPrototypeOf(value);
469
+ return proto === Object.prototype || proto === null;
470
+ }
471
+ function isClassifiedKey(key) {
472
+ return Object.hasOwn(XAI_PROVIDER_OPTION_CLASSIFICATION, key);
473
+ }
474
+ function isGrokEffort(value) {
475
+ return value === "low" || value === "medium" || value === "high" || value === "xhigh";
476
+ }
477
+ function grokEffortsForModel(modelId) {
478
+ const backendId = modelId.startsWith("grok/") ? modelId.slice(5) : modelId;
479
+ return GROK_MODEL_EFFORTS.get(backendId) ?? DEFAULT_GROK_EFFORTS;
480
+ }
481
+ /**
482
+ * Validates `providerOptions.xai` (the `@ai-sdk/xai` option shape) and maps the keys qgrid honors
483
+ * onto its request fields. Unsupported keys and values become warnings; malformed values throw
484
+ * `QgridXaiOptionsError`.
485
+ */
486
+ function mapXaiProviderOptions(options, modelId) {
487
+ const result = {
488
+ request: {},
489
+ warnings: [],
490
+ rejected: []
491
+ };
492
+ if (options === void 0 || options === null) return result;
493
+ if (!isPlainJsonObject(options)) throw new QgridXaiOptionsError([{
494
+ path: "",
495
+ message: "expected an object"
496
+ }]);
497
+ const parsed = qgridXaiProviderOptionsSchema.safeParse(options);
498
+ if (!parsed.success) throw new QgridXaiOptionsError(parsed.error.issues.map((issue) => ({
499
+ path: issue.path.map(String).join("."),
500
+ message: issue.message
501
+ })));
502
+ const values = parsed.data;
503
+ for (const key of Object.keys(options)) {
504
+ if (!isClassifiedKey(key)) {
505
+ result.warnings.push({
506
+ key,
507
+ reason: "unknown providerOptions.xai key; ignored by qgrid"
508
+ });
509
+ continue;
510
+ }
511
+ const value = values[key];
512
+ if (value === void 0 || value === null) continue;
513
+ const classification = XAI_PROVIDER_OPTION_CLASSIFICATION[key];
514
+ if (classification.kind === "rejected") {
515
+ result.warnings.push({
516
+ key,
517
+ reason: classification.reason
518
+ });
519
+ result.rejected.push(key);
520
+ }
521
+ }
522
+ const effort = values.reasoningEffort;
523
+ if (effort !== void 0) if (effort === "none") {
524
+ result.warnings.push({
525
+ key: "reasoningEffort",
526
+ reason: "reasoningEffort \"none\" is not supported; the qgrid Grok route always reasons"
527
+ });
528
+ result.rejected.push("reasoningEffort");
529
+ } else if (isGrokEffort(effort) && grokEffortsForModel(modelId).includes(effort)) result.request.effort = effort;
530
+ else {
531
+ result.warnings.push({
532
+ key: "reasoningEffort",
533
+ reason: `reasoningEffort "${effort}" is not supported by ${modelId}`
534
+ });
535
+ result.rejected.push("reasoningEffort");
536
+ }
537
+ if (values.promptCacheKey !== void 0) result.request.promptCacheKey = values.promptCacheKey;
538
+ return result;
539
+ }
540
+ //#endregion
256
541
  //#region src/http-transport.ts
257
542
  const DEFAULT_ANTHROPIC_TIMEOUT_MS = 24e4;
258
543
  const TRANSPORT_RESPONSE_GRACE_MS = 6e4;
544
+ const errorChainLinkSchema = z.looseObject({
545
+ cause: z.unknown().optional(),
546
+ code: z.unknown().optional(),
547
+ errors: z.unknown().optional(),
548
+ message: z.unknown().optional()
549
+ });
550
+ const nestedErrorsSchema = z.array(z.unknown());
259
551
  function anthropicTransportOptions(timeoutMs) {
260
552
  const transportTimeoutMs = (Number.isFinite(timeoutMs) && timeoutMs !== void 0 && timeoutMs > 0 ? timeoutMs : DEFAULT_ANTHROPIC_TIMEOUT_MS) + TRANSPORT_RESPONSE_GRACE_MS;
261
553
  return {
@@ -282,49 +574,51 @@ async function qgridFetch(input, init, context) {
282
574
  throw toQgridTransportError(error, context);
283
575
  }
284
576
  }
285
- function toQgridTransportError(error, context) {
286
- if (isAbortError(error)) return error;
287
- const code = findErrorCode(error);
577
+ function toQgridTransportError(cause, context) {
578
+ if (isAbortError$1(cause)) return cause;
579
+ const code = findErrorCode(cause);
288
580
  const prefix = `qgrid ${context.operation} transport failed`;
289
581
  if (code === "UND_ERR_HEADERS_TIMEOUT") {
290
582
  const budget = context.transportTimeoutMs ? ` after ${context.transportTimeoutMs}ms` : "";
291
- return new Error(`${prefix}: response headers timed out${budget} (${code})`, { cause: error });
583
+ return new Error(`${prefix}: response headers timed out${budget} (${code})`, { cause });
292
584
  }
293
585
  if (code === "UND_ERR_BODY_TIMEOUT") {
294
586
  const budget = context.transportTimeoutMs ? ` after ${context.transportTimeoutMs}ms` : "";
295
- return new Error(`${prefix}: response body timed out${budget} (${code})`, { cause: error });
587
+ return new Error(`${prefix}: response body timed out${budget} (${code})`, { cause });
296
588
  }
297
- if (code === "ECONNREFUSED") return new Error(`${prefix}: connection refused by ${serverOrigin(context.serverUrl)} (${code})`, { cause: error });
298
- const detail = findDeepestMessage(error) ?? "unknown network error";
299
- return new Error(`${prefix}: ${detail}${code ? ` (${code})` : ""}`, { cause: error });
589
+ if (code === "ECONNREFUSED") return new Error(`${prefix}: connection refused by ${serverOrigin(context.serverUrl)} (${code})`, { cause });
590
+ const detail = findDeepestMessage(cause) ?? "unknown network error";
591
+ return new Error(`${prefix}: ${detail}${code ? ` (${code})` : ""}`, { cause });
300
592
  }
301
- function isAbortError(error) {
593
+ function isAbortError$1(error) {
302
594
  return error instanceof Error && error.name === "AbortError";
303
595
  }
304
- function findErrorCode(error, seen = /* @__PURE__ */ new Set()) {
305
- if (!isErrorLike(error) || seen.has(error)) return void 0;
306
- seen.add(error);
307
- if (typeof error.code === "string") return error.code;
308
- const causeCode = findErrorCode(error.cause, seen);
596
+ function findErrorCode(cause, seen = /* @__PURE__ */ new Set()) {
597
+ const link = errorChainLinkSchema.safeParse(cause);
598
+ if (!link.success || seen.has(cause)) return void 0;
599
+ seen.add(cause);
600
+ const code = z.string().safeParse(link.data.code);
601
+ if (code.success) return code.data;
602
+ const causeCode = findErrorCode(link.data.cause, seen);
309
603
  if (causeCode) return causeCode;
310
- if (Array.isArray(error.errors)) for (const nested of error.errors) {
311
- const nestedCode = findErrorCode(nested, seen);
604
+ const nested = nestedErrorsSchema.safeParse(link.data.errors);
605
+ if (nested.success) for (const nestedError of nested.data) {
606
+ const nestedCode = findErrorCode(nestedError, seen);
312
607
  if (nestedCode) return nestedCode;
313
608
  }
314
609
  }
315
- function findDeepestMessage(error, seen = /* @__PURE__ */ new Set()) {
316
- if (!isErrorLike(error) || seen.has(error)) return void 0;
317
- seen.add(error);
318
- const causeMessage = findDeepestMessage(error.cause, seen);
610
+ function findDeepestMessage(cause, seen = /* @__PURE__ */ new Set()) {
611
+ const link = errorChainLinkSchema.safeParse(cause);
612
+ if (!link.success || seen.has(cause)) return void 0;
613
+ seen.add(cause);
614
+ const causeMessage = findDeepestMessage(link.data.cause, seen);
319
615
  if (causeMessage) return causeMessage;
320
- if (Array.isArray(error.errors)) for (const nested of error.errors) {
321
- const nestedMessage = findDeepestMessage(nested, seen);
616
+ const nested = nestedErrorsSchema.safeParse(link.data.errors);
617
+ if (nested.success) for (const nestedError of nested.data) {
618
+ const nestedMessage = findDeepestMessage(nestedError, seen);
322
619
  if (nestedMessage) return nestedMessage;
323
620
  }
324
- return typeof error.message === "string" && error.message.length > 0 ? error.message : void 0;
325
- }
326
- function isErrorLike(value) {
327
- return typeof value === "object" && value !== null;
621
+ return z.string().min(1).safeParse(link.data.message).data;
328
622
  }
329
623
  function serverOrigin(serverUrl) {
330
624
  try {
@@ -342,33 +636,52 @@ const TOP_LEVEL_SCHEMA_WARNING = "qgrid server-enforced structured output requir
342
636
  const PENDING_TOOL_RESULTS_WARNING = "[qgrid] tool results did not match any pending qgrid run; sending request without runContext";
343
637
  //#endregion
344
638
  //#region src/utils.ts
639
+ const createRunOutputSchema = z.object({ requestLogId: z.number() });
640
+ const appendStepOutputSchema = z.object({ stepId: z.number() });
641
+ const finishRunOutputSchema = z.object({ ok: z.boolean() });
345
642
  async function createRun(serverUrl, body) {
346
- return (await fetch(`${serverUrl}/api/qgrid/createRun`, {
643
+ const res = await fetch(`${serverUrl}/api/qgrid/createRun`, {
347
644
  method: "POST",
348
645
  headers: { "Content-Type": "application/json" },
349
646
  body: JSON.stringify({ input: body })
350
- })).json();
647
+ });
648
+ return createRunOutputSchema.parse(await res.json());
351
649
  }
352
650
  async function appendStep(serverUrl, body) {
353
- return (await fetch(`${serverUrl}/api/qgrid/appendStep`, {
651
+ const res = await fetch(`${serverUrl}/api/qgrid/appendStep`, {
354
652
  method: "POST",
355
653
  headers: { "Content-Type": "application/json" },
356
654
  body: JSON.stringify({ input: body })
357
- })).json();
655
+ });
656
+ return appendStepOutputSchema.parse(await res.json());
358
657
  }
359
658
  async function finishRun(serverUrl, body) {
360
- return (await fetch(`${serverUrl}/api/qgrid/finishRun`, {
659
+ const res = await fetch(`${serverUrl}/api/qgrid/finishRun`, {
361
660
  method: "POST",
362
661
  headers: { "Content-Type": "application/json" },
363
662
  body: JSON.stringify({ input: body })
364
- })).json();
663
+ });
664
+ return finishRunOutputSchema.parse(await res.json());
365
665
  }
666
+ /**
667
+ * Decodes an AI SDK payload (tool input, tool output, tool error) into request-log text. JSON
668
+ * cannot encode every JavaScript value (cycles, BigInt, bare undefined); those fall back to String().
669
+ */
670
+ const loggedPayloadText = z.unknown().transform((payload) => {
671
+ try {
672
+ const json = JSON.stringify(payload);
673
+ return json === void 0 ? String(payload) : json;
674
+ } catch {
675
+ return String(payload);
676
+ }
677
+ });
678
+ /** Like loggedPayloadText, but keeps strings verbatim (tool arguments that are already JSON text). */
679
+ const toolPayloadText = z.union([z.string(), loggedPayloadText]);
366
680
  function toQgridTool(tool) {
367
- const source = tool;
368
681
  return {
369
682
  name: tool.name,
370
683
  description: tool.description,
371
- inputSchema: source.inputSchema ?? source.parameters ?? {}
684
+ inputSchema: tool.inputSchema
372
685
  };
373
686
  }
374
687
  function extractToolResultsFromHistory(messages) {
@@ -377,13 +690,13 @@ function extractToolResultsFromHistory(messages) {
377
690
  for (const msg of messages) if (msg.role === "assistant") {
378
691
  for (const part of msg.content) if ("type" in part && part.type === "tool-call") calls.set(part.toolCallId, {
379
692
  toolName: part.toolName,
380
- args: typeof part.input === "string" ? part.input : JSON.stringify(part.input)
693
+ args: toolPayloadText.parse(part.input)
381
694
  });
382
695
  } else if (msg.role === "tool") {
383
696
  for (const part of msg.content) if ("type" in part && part.type === "tool-result") {
384
697
  const id = "toolCallId" in part ? part.toolCallId : "";
385
698
  const output = part.output;
386
- const text = "value" in output ? typeof output.value === "string" ? output.value : JSON.stringify(output.value) : JSON.stringify(output);
699
+ const text = "value" in output ? toolPayloadText.parse(output.value) : JSON.stringify(output);
387
700
  results.set(id, text);
388
701
  }
389
702
  }
@@ -446,7 +759,7 @@ function extractPromptAndHistory(messages, options = {}) {
446
759
  droppedImageCount += extracted.droppedImageCount;
447
760
  return extracted;
448
761
  };
449
- for (const msg of messages) if (msg.role === "system") system = extractPromptParts(msg.content, false).text;
762
+ for (const msg of messages) if (msg.role === "system") system = msg.content;
450
763
  else nonSystem.push(msg);
451
764
  if (nonSystem.length === 0) return {
452
765
  prompt: "",
@@ -479,7 +792,7 @@ function extractPromptAndHistory(messages, options = {}) {
479
792
  }]
480
793
  });
481
794
  } else if (msg.role === "assistant") {
482
- for (const part of msg.content) if ("text" in part && typeof part.text === "string") history.push({
795
+ for (const part of msg.content) if ("text" in part) history.push({
483
796
  type: "message",
484
797
  role: "assistant",
485
798
  content: [{
@@ -490,14 +803,14 @@ function extractPromptAndHistory(messages, options = {}) {
490
803
  else if ("toolName" in part && part.type === "tool-call") history.push({
491
804
  type: "function_call",
492
805
  name: part.toolName,
493
- arguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input),
806
+ arguments: toolPayloadText.parse(part.input),
494
807
  call_id: part.toolCallId
495
808
  });
496
809
  else if (isImagePart(part)) droppedImageCount++;
497
810
  } else if (msg.role === "tool") {
498
811
  for (const part of msg.content) if ("toolName" in part && part.type === "tool-result") {
499
812
  const output = part.output;
500
- const text = "value" in output ? typeof output.value === "string" ? output.value : JSON.stringify(output.value) : JSON.stringify(output);
813
+ const text = "value" in output ? toolPayloadText.parse(output.value) : JSON.stringify(output);
501
814
  history.push({
502
815
  type: "function_call_output",
503
816
  call_id: ("toolCallId" in part ? part.toolCallId : "") ?? "",
@@ -506,30 +819,22 @@ function extractPromptAndHistory(messages, options = {}) {
506
819
  }
507
820
  }
508
821
  }
509
- return {
822
+ const result = {
510
823
  prompt,
511
824
  system,
512
825
  history,
513
826
  imageUrls,
514
- droppedImageCount,
515
- ...input ? { input } : {}
827
+ droppedImageCount
516
828
  };
829
+ if (input) result.input = input;
830
+ return result;
517
831
  }
518
832
  function extractPromptParts(content, includeImages) {
519
- if (typeof content === "string") return {
520
- text: content,
521
- parts: [{
522
- type: "input_text",
523
- text: content
524
- }],
525
- imageUrls: [],
526
- droppedImageCount: 0
527
- };
528
833
  const parts = [];
529
834
  const texts = [];
530
835
  const imageUrls = [];
531
836
  let droppedImageCount = 0;
532
- for (const part of content) if ("text" in part && typeof part.text === "string") {
837
+ for (const part of content) if ("text" in part) {
533
838
  parts.push({
534
839
  type: "input_text",
535
840
  text: part.text
@@ -567,18 +872,13 @@ function inputPartsFromPromptParts(parts) {
567
872
  });
568
873
  }
569
874
  function isImagePart(part) {
570
- if (!part || typeof part !== "object") return false;
571
- const record = part;
572
- return record.type === "image" || record.type === "file" && typeof record.mediaType === "string" && record.mediaType.toLowerCase().startsWith("image/");
875
+ return part.type === "file" && part.mediaType.toLowerCase().startsWith("image/");
573
876
  }
574
877
  function imageUrl(part) {
575
- const record = part;
576
- const mediaType = typeof record.mediaType === "string" && record.mediaType !== "image/*" ? record.mediaType : "image/png";
577
- const data = record.data ?? record.image;
878
+ const mediaType = part.mediaType === "image/*" ? "image/png" : part.mediaType;
879
+ const { data } = part;
578
880
  if (data instanceof URL) return /^https?:$/i.test(data.protocol) ? data.toString() : void 0;
579
881
  if (data instanceof Uint8Array) return `data:${mediaType};base64,${bytesToBase64(data)}`;
580
- if (data instanceof ArrayBuffer) return `data:${mediaType};base64,${bytesToBase64(new Uint8Array(data))}`;
581
- if (typeof data !== "string") return void 0;
582
882
  if (/^(https?:|data:)/i.test(data)) return data;
583
883
  return /^[a-z][a-z0-9+.-]*:/i.test(data) ? void 0 : `data:${mediaType};base64,${data}`;
584
884
  }
@@ -600,73 +900,63 @@ function assertImageInputFitsJsonTransport(imageUrls) {
600
900
  }
601
901
  if (totalDataUrlChars > MAX_IMAGE_INPUT_DATA_URL_CHARS) throw new Error(`qgrid: image inputs are too large for JSON transport (${totalDataUrlChars} total chars). Compress or resize them before passing them to qgrid, for example as WebP/JPEG under ${MAX_IMAGE_INPUT_DATA_URL_CHARS_LABEL} total base64 data-url chars.`);
602
902
  }
603
- function safeStringify(value) {
604
- try {
605
- const json = JSON.stringify(value);
606
- return json === void 0 ? String(value) : json;
607
- } catch {
608
- return String(value);
609
- }
610
- }
611
- function getRecord(value) {
612
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
613
- }
614
903
  function extractTextContent(content) {
615
- if (typeof content === "string") return content;
904
+ if (!Array.isArray(content)) return content;
616
905
  const parts = [];
617
- if (Array.isArray(content)) {
618
- for (const part of content) if (part && typeof part === "object" && "type" in part) {
619
- if (part.type === "text" && "text" in part && typeof part.text === "string") parts.push(part.text);
620
- }
621
- }
906
+ for (const part of content) if (part.type === "text") parts.push(part.text);
622
907
  return parts.join("\n");
623
908
  }
624
- function getErrorMessage(value) {
625
- if (value instanceof Error) return String(value);
626
- if (value === void 0 || value === null) return "unknown error";
627
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
909
+ const primitiveErrorSchema = z.union([
910
+ z.string(),
911
+ z.number(),
912
+ z.nan(),
913
+ z.boolean(),
914
+ z.bigint()
915
+ ]);
916
+ /** Formats an error cause (thrown value, abort reason, AI SDK error field) for a request log. */
917
+ function getErrorMessage(cause) {
918
+ if (cause instanceof Error) return String(cause);
919
+ if (cause === void 0 || cause === null) return "unknown error";
920
+ const primitive = primitiveErrorSchema.safeParse(cause);
921
+ if (primitive.success) return String(primitive.data);
628
922
  try {
629
- return JSON.stringify(value) ?? "unknown error";
923
+ return JSON.stringify(cause) ?? "unknown error";
630
924
  } catch {
631
925
  return "unknown error";
632
926
  }
633
927
  }
634
928
  function extractUserPrompt(prompt, messages) {
635
- if (typeof prompt === "string") return prompt;
636
- const messageList = Array.isArray(messages) ? messages : Array.isArray(prompt) ? prompt : [];
929
+ if (prompt !== void 0 && !Array.isArray(prompt)) return prompt;
930
+ const messageList = messages ?? prompt ?? [];
637
931
  for (let i = messageList.length - 1; i >= 0; i--) {
638
- const msg = getRecord(messageList[i]);
932
+ const msg = messageList[i];
639
933
  if (msg?.role === "user") return extractTextContent(msg.content);
640
934
  }
641
935
  return "";
642
936
  }
643
937
  function extractSystemPrompt(system) {
644
- if (typeof system === "string") return system;
645
- if (system && typeof system === "object" && "content" in system) {
646
- const content = system.content;
647
- if (typeof content === "string") return content;
648
- }
938
+ if (system === void 0 || Array.isArray(system)) return void 0;
939
+ return system instanceof Object ? system.content : system;
649
940
  }
650
941
  function serializeHistory(messages) {
651
- if (!Array.isArray(messages) || messages.length === 0) return void 0;
652
- const sliced = getRecord(messages[messages.length - 1])?.role === "user" ? messages.slice(0, -1) : messages;
942
+ if (!messages || messages.length === 0) return void 0;
943
+ const sliced = messages.at(-1)?.role === "user" ? messages.slice(0, -1) : messages;
653
944
  const history = [];
654
945
  for (const msg of sliced) {
655
- const record = getRecord(msg);
656
- if (record?.role !== "user" && record?.role !== "assistant") continue;
657
- const text = extractTextContent(record.content);
946
+ if (msg.role !== "user" && msg.role !== "assistant") continue;
947
+ const text = extractTextContent(msg.content);
658
948
  if (text.length === 0) continue;
659
949
  history.push({
660
950
  type: "message",
661
- role: record.role,
951
+ role: msg.role,
662
952
  content: [{
663
- type: record.role === "user" ? "input_text" : "output_text",
953
+ type: msg.role === "user" ? "input_text" : "output_text",
664
954
  text
665
955
  }]
666
956
  });
667
957
  }
668
958
  if (history.length === 0) return void 0;
669
- return safeStringify(history);
959
+ return JSON.stringify(history);
670
960
  }
671
961
  //#endregion
672
962
  //#region src/image-model.ts
@@ -680,10 +970,198 @@ function imageInputUrl(file) {
680
970
  "image/jpeg",
681
971
  "image/webp"
682
972
  ].includes(file.mediaType)) throw new Error("qgrid.image: reference images must be PNG, JPEG, or WebP");
683
- const data = typeof file.data === "string" ? file.data : bytesToBase64(file.data);
973
+ const data = file.data instanceof Uint8Array ? bytesToBase64(file.data) : file.data;
684
974
  return `data:${file.mediaType};base64,${data}`;
685
975
  }
976
+ /** Server-side Fastify body limit (`sonamu.config.ts` `bodyLimit`). Larger bodies get a 413. */
977
+ const QGRID_MAX_REQUEST_BODY_BYTES = 10 * 1024 * 1024;
978
+ function resolveMediaConfig(config) {
979
+ return {
980
+ serverUrl: config?.serverUrl ?? process.env.QGRID_URL ?? "http://localhost:44900",
981
+ projectName: config?.projectName ?? process.env.QGRID_PROJECT_NAME
982
+ };
983
+ }
984
+ const GROK_MODEL_ID = /^grok\/[A-Za-z0-9._-]+$/;
985
+ function assertGrokModelId(modelId, label) {
986
+ if (!GROK_MODEL_ID.test(modelId)) throw new Error(`${label}: unsupported model ${modelId}; expected grok/<id>`);
987
+ }
988
+ function formatMiB(bytes) {
989
+ return `${(bytes / 1024 / 1024).toFixed(2)} MiB`;
990
+ }
991
+ /**
992
+ * Server `MEDIA_MAX_INLINE_BYTES` (qgrid.types.ts): decoded inline audio, each inline image, and all
993
+ * inline images of one request together. Derived from the 10 MiB body limit: base64 grows bytes by
994
+ * 4/3, so 7 MiB decoded is about 9.3 MiB encoded, leaving room for the rest of the JSON body.
995
+ */
996
+ const QGRID_MAX_INLINE_MEDIA_BYTES = 7 * 1024 * 1024;
997
+ function base64DecodedBytes(data) {
998
+ const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
999
+ return Math.floor(data.length * 3 / 4) - padding;
1000
+ }
1001
+ /**
1002
+ * Rejects inline media above the server's decoded-size cap before transport. Accepts base64
1003
+ * strings and data URLs; https URLs cost nothing because xAI fetches them.
1004
+ */
1005
+ function assertInlineMediaFits(items, label) {
1006
+ let total = 0;
1007
+ for (const item of items) {
1008
+ if (item === void 0 || /^https:\/\//i.test(item)) continue;
1009
+ const comma = item.startsWith("data:") ? item.indexOf(",") : -1;
1010
+ total += base64DecodedBytes(comma >= 0 ? item.slice(comma + 1) : item);
1011
+ }
1012
+ 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.`);
1013
+ }
1014
+ /**
1015
+ * Serializes a Sonamu `params` body and rejects it before transport when it would exceed the
1016
+ * server body limit, so callers get a clear error instead of an opaque 413.
1017
+ */
1018
+ function serializeMediaBody(params, label) {
1019
+ const body = JSON.stringify({ params });
1020
+ const bytes = new TextEncoder().encode(body).byteLength;
1021
+ 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.`);
1022
+ return body;
1023
+ }
1024
+ const ERROR_BODY_PREVIEW_CHARS = 2e3;
1025
+ function isAbortError(cause) {
1026
+ return cause instanceof Error && (cause.name === "AbortError" || cause.name === "TimeoutError");
1027
+ }
1028
+ /**
1029
+ * Rethrows `cause` when it is an abort. When `signal` fired, its own reason is thrown so callers
1030
+ * that compare reasons (the video poll deadline) can tell which abort it was.
1031
+ */
1032
+ function rethrowAbort(cause, signal) {
1033
+ if (signal?.aborted) throw signal.reason ?? cause;
1034
+ if (isAbortError(cause)) throw cause;
1035
+ }
1036
+ /** Non-2xx response from a qgrid media endpoint. */
1037
+ var QgridMediaHttpError = class extends Error {
1038
+ status;
1039
+ constructor(message, status) {
1040
+ super(message);
1041
+ this.name = "QgridMediaHttpError";
1042
+ this.status = status;
1043
+ }
1044
+ };
1045
+ /** POSTs one Sonamu media endpoint and validates the JSON response with `schema`. */
1046
+ async function postMediaEndpoint(args) {
1047
+ const headers = new Headers();
1048
+ for (const [key, value] of Object.entries(args.headers ?? {})) if (value !== void 0) headers.set(key, value);
1049
+ headers.set("Content-Type", "application/json");
1050
+ const init = {
1051
+ method: "POST",
1052
+ headers,
1053
+ body: args.body
1054
+ };
1055
+ if (args.signal) init.signal = args.signal;
1056
+ let response;
1057
+ try {
1058
+ response = await fetch(`${args.serverUrl}/api/qgrid/${args.endpoint}`, init);
1059
+ } catch (error) {
1060
+ rethrowAbort(error, args.signal);
1061
+ const detail = error instanceof Error ? error.message : String(error);
1062
+ throw new Error(`${args.label}: ${args.endpoint} transport failed: ${detail}`, { cause: error });
1063
+ }
1064
+ if (!response.ok) {
1065
+ const text = await response.text().catch((cause) => {
1066
+ rethrowAbort(cause, args.signal);
1067
+ return "";
1068
+ });
1069
+ if (response.status === 413) throw new QgridMediaHttpError(`${args.label}: the qgrid server rejected the request as too large (413); inline media is limited to ${formatMiB(QGRID_MAX_INLINE_MEDIA_BYTES)} decoded per request. ${text.slice(0, ERROR_BODY_PREVIEW_CHARS)}`, 413);
1070
+ throw new QgridMediaHttpError(`qgrid ${args.endpoint} ${response.status}: ${text.slice(0, ERROR_BODY_PREVIEW_CHARS)}`, response.status);
1071
+ }
1072
+ const json = await response.json().catch((cause) => {
1073
+ rethrowAbort(cause, args.signal);
1074
+ });
1075
+ const parsed = args.schema.safeParse(json);
1076
+ if (!parsed.success) throw new Error(`${args.label}: unexpected ${args.endpoint} response; update the qgrid server and SDK together (${formatIssues(parsed.error)})`);
1077
+ return {
1078
+ data: parsed.data,
1079
+ headers: Object.fromEntries(response.headers)
1080
+ };
1081
+ }
1082
+ function formatIssues(error) {
1083
+ return error.issues.map((issue) => `${issue.path.map(String).join(".") || "(root)"}: ${issue.message}`).join("; ");
1084
+ }
1085
+ const grokQgridOptionsSchema = z.object({
1086
+ tokenName: z.string().refine((value) => /^grok\/.+/.test(value.trim()), { message: "tokenName must be a non-empty Grok token name with the grok/ prefix" }).optional(),
1087
+ logger: z.boolean({ message: "logger must be a boolean" }).optional()
1088
+ });
1089
+ /** Any non-null, non-array object; field validation happens in the option schemas. */
1090
+ const providerOptionsObjectSchema = z.looseObject({});
1091
+ function parseGrokQgridOptions(raw, label) {
1092
+ if (raw === void 0) return {};
1093
+ if (!providerOptionsObjectSchema.safeParse(raw).success) throw new Error(`${label}: providerOptions.qgrid must be an object`);
1094
+ const parsed = grokQgridOptionsSchema.safeParse(raw);
1095
+ if (!parsed.success) throw new Error(`${label}: ${parsed.error.issues.map((issue) => issue.message).join("; ")}`);
1096
+ const request = {};
1097
+ if (parsed.data.tokenName !== void 0) request.tokenName = parsed.data.tokenName;
1098
+ if (parsed.data.logger === false) request.logger = false;
1099
+ return request;
1100
+ }
1101
+ /**
1102
+ * Validates `providerOptions.xai` against `schema` and warns about every set key outside
1103
+ * `honored`. Malformed values throw; documented-but-unsupported keys become warnings.
1104
+ */
1105
+ function parseXaiMediaOptions(raw, schema, honored, label) {
1106
+ if (raw === void 0) return {
1107
+ options: void 0,
1108
+ warnings: []
1109
+ };
1110
+ const record = providerOptionsObjectSchema.safeParse(raw);
1111
+ if (!record.success) throw new Error(`${label}: providerOptions.xai must be an object`);
1112
+ const parsed = schema.safeParse(raw);
1113
+ if (!parsed.success) throw new Error(`${label}: invalid providerOptions.xai: ${formatIssues(parsed.error)}`);
1114
+ const warnings = [];
1115
+ for (const [key, value] of Object.entries(record.data)) {
1116
+ if (value === void 0 || value === null || honored.has(key)) continue;
1117
+ warnings.push({
1118
+ type: "unsupported",
1119
+ feature: `providerOptions.xai.${key}`,
1120
+ details: `qgrid does not support providerOptions.xai.${key} for this model; it was ignored.`
1121
+ });
1122
+ }
1123
+ return {
1124
+ options: parsed.data,
1125
+ warnings
1126
+ };
1127
+ }
1128
+ const IMAGE_REF_MEDIA_TYPES = [
1129
+ "image/png",
1130
+ "image/jpeg",
1131
+ "image/webp"
1132
+ ];
1133
+ /** An AI SDK file as the server's image reference: an https URL or a base64 image data URL. */
1134
+ function toImageRef(file, label) {
1135
+ if (file.type === "url") return toImageRefUrl(file.url, label);
1136
+ const mediaType = file.mediaType.toLowerCase();
1137
+ if (!IMAGE_REF_MEDIA_TYPES.includes(mediaType)) throw new Error(`${label}: reference images must be PNG, JPEG, or WebP`);
1138
+ return `data:${mediaType};base64,${file.data instanceof Uint8Array ? bytesToBase64(file.data) : file.data}`;
1139
+ }
1140
+ /** Accepts an https URL or a base64 PNG/JPEG/WebP data URL string. */
1141
+ function toImageRefUrl(url, label) {
1142
+ if (/^https:\/\//i.test(url)) return url;
1143
+ if (/^data:image\/(png|jpeg|webp);base64,/i.test(url)) return url;
1144
+ throw new Error(`${label}: image references must be https URLs or PNG/JPEG/WebP data URLs`);
1145
+ }
1146
+ const OPENAI_IMAGE_TOKEN_NAME_MESSAGE = "tokenName must be a non-empty OpenAI token name with the openai/ prefix";
1147
+ /** `providerOptions.qgrid` for `qgrid.image("openai/gpt-image-2")`; issues keep this field order. */
1148
+ const openAIImageQgridOptionsSchema = z.object({
1149
+ tokenName: z.string({ message: OPENAI_IMAGE_TOKEN_NAME_MESSAGE }).refine((value) => /^openai\/.+/.test(value.trim()), { message: OPENAI_IMAGE_TOKEN_NAME_MESSAGE }).optional(),
1150
+ logger: z.boolean({ message: "logger must be a boolean" }).optional(),
1151
+ outputFormat: z.literal("png", { message: "the subscription Images endpoint supports PNG output only" }).optional(),
1152
+ quality: z.enum([
1153
+ "low",
1154
+ "medium",
1155
+ "high"
1156
+ ], { message: "quality must be low, medium, or high" }).optional(),
1157
+ background: z.enum([
1158
+ "auto",
1159
+ "opaque",
1160
+ "transparent"
1161
+ ], { message: "background must be auto, opaque, or transparent" }).optional()
1162
+ });
686
1163
  function createQgridImage(modelId, config) {
1164
+ if (modelId.startsWith("grok/")) return createGrokImage(modelId, config);
687
1165
  if (modelId !== "openai/gpt-image-2") throw new Error(`qgrid.image: unsupported image model ${modelId}`);
688
1166
  const serverUrl = config?.serverUrl ?? process.env.QGRID_URL ?? "http://localhost:44900";
689
1167
  const projectName = config?.projectName ?? process.env.QGRID_PROJECT_NAME;
@@ -697,20 +1175,9 @@ function createQgridImage(modelId, config) {
697
1175
  if (options.n !== 1) throw new Error("qgrid.image: only one image per call is supported; omit maxImagesPerCall so AI SDK can batch n images");
698
1176
  if (!options.prompt?.trim()) throw new Error("qgrid.image: a non-empty text prompt is required");
699
1177
  if ((options.files?.length ?? 0) > 5) throw new Error("qgrid.image: at most five reference images are supported");
700
- const qgridOptions = options.providerOptions.qgrid ?? {};
701
- if (qgridOptions.tokenName !== void 0 && (typeof qgridOptions.tokenName !== "string" || !/^openai\/.+/.test(qgridOptions.tokenName.trim()))) throw new Error("qgrid.image: tokenName must be a non-empty OpenAI token name with the openai/ prefix");
702
- if (qgridOptions.logger !== void 0 && typeof qgridOptions.logger !== "boolean") throw new Error("qgrid.image: logger must be a boolean");
703
- if (qgridOptions.outputFormat !== void 0 && qgridOptions.outputFormat !== "png") throw new Error("qgrid.image: the subscription Images endpoint supports PNG output only");
704
- if (qgridOptions.quality !== void 0 && ![
705
- "low",
706
- "medium",
707
- "high"
708
- ].includes(qgridOptions.quality)) throw new Error("qgrid.image: quality must be low, medium, or high");
709
- if (qgridOptions.background !== void 0 && ![
710
- "auto",
711
- "opaque",
712
- "transparent"
713
- ].includes(qgridOptions.background)) throw new Error("qgrid.image: background must be auto, opaque, or transparent");
1178
+ const parsedQgridOptions = openAIImageQgridOptionsSchema.safeParse(options.providerOptions.qgrid ?? {});
1179
+ if (!parsedQgridOptions.success) throw new Error(`qgrid.image: ${parsedQgridOptions.error.issues[0]?.message}`);
1180
+ const qgridOptions = parsedQgridOptions.data;
714
1181
  if (options.size !== void 0 && !/^[1-9]\d*x[1-9]\d*$/.test(options.size)) throw new Error("qgrid.image: size must be positive integer WIDTHxHEIGHT");
715
1182
  const imageUrls = (options.files ?? []).map(imageInputUrl);
716
1183
  assertImageInputFitsJsonTransport(imageUrls);
@@ -728,7 +1195,7 @@ function createQgridImage(modelId, config) {
728
1195
  feature,
729
1196
  details: "The subscription Images endpoint does not support this setting. Describe the desired result in the prompt."
730
1197
  });
731
- for (const feature of [...options.size !== void 0 ? ["size"] : [], ...qgridOptions.quality !== void 0 ? ["providerOptions.qgrid.quality"] : []]) warnings.push({
1198
+ for (const feature of [...options.size === void 0 ? [] : ["size"], ...qgridOptions.quality === void 0 ? [] : ["providerOptions.qgrid.quality"]]) warnings.push({
732
1199
  type: "compatibility",
733
1200
  feature,
734
1201
  details: "This is a hint only. The subscription backend may choose a different size or quality; inspect the returned generation metadata."
@@ -737,53 +1204,753 @@ function createQgridImage(modelId, config) {
737
1204
  const headers = new Headers();
738
1205
  for (const [key, value] of Object.entries(options.headers ?? {})) if (value !== void 0) headers.set(key, value);
739
1206
  headers.set("Content-Type", "application/json");
1207
+ const imageGenerationOptions = {};
1208
+ if (options.size !== void 0) imageGenerationOptions.size = options.size;
1209
+ if (qgridOptions.quality !== void 0) imageGenerationOptions.quality = qgridOptions.quality;
1210
+ if (qgridOptions.background !== void 0) imageGenerationOptions.background = qgridOptions.background;
1211
+ if (qgridOptions.outputFormat !== void 0) imageGenerationOptions.outputFormat = qgridOptions.outputFormat;
1212
+ const args = {
1213
+ prompt: options.prompt,
1214
+ model: modelId,
1215
+ imageGeneration: true,
1216
+ imageGenerationOptions
1217
+ };
1218
+ if (imageUrls.length) args.input = input;
1219
+ if (projectName) args.projectName = projectName;
1220
+ if (qgridOptions.tokenName !== void 0) args.tokenName = qgridOptions.tokenName;
1221
+ if (qgridOptions.logger === false) args.logger = false;
740
1222
  const response = await qgridFetch(`${serverUrl}/api/qgrid/query`, {
741
1223
  method: "POST",
742
1224
  headers,
743
1225
  signal: options.abortSignal,
744
- body: JSON.stringify({ args: {
745
- prompt: options.prompt,
746
- model: modelId,
747
- imageGeneration: true,
748
- imageGenerationOptions: {
749
- ...options.size !== void 0 ? { size: options.size } : {},
750
- ...qgridOptions.quality !== void 0 ? { quality: qgridOptions.quality } : {},
751
- ...qgridOptions.background !== void 0 ? { background: qgridOptions.background } : {},
752
- ...qgridOptions.outputFormat !== void 0 ? { outputFormat: qgridOptions.outputFormat } : {}
753
- },
754
- ...imageUrls.length ? { input } : {},
755
- ...projectName ? { projectName } : {},
756
- ...qgridOptions.tokenName !== void 0 ? { tokenName: qgridOptions.tokenName } : {},
757
- ...qgridOptions.logger === false ? { logger: false } : {}
758
- } })
1226
+ body: JSON.stringify({ args })
759
1227
  }, {
760
1228
  operation: "query",
761
1229
  serverUrl
762
1230
  });
763
1231
  if (!response.ok) throw new Error(`qgrid ${response.status}: ${await response.text().catch(() => "")}`);
764
- const data = await response.json();
1232
+ const data = queryOutputSchema.parse(await response.json());
765
1233
  const images = data.content?.filter((item) => item.type === "image") ?? [];
766
1234
  const image = images[0];
767
1235
  if (images.length !== 1 || !image?.data || image.generation?.route !== "codex-images") throw new Error("qgrid.image: expected one image from the direct subscription Images route; update the qgrid server and SDK together");
768
1236
  const usage = image.generation.usage;
769
- return {
1237
+ const imageMetadata = {};
1238
+ if (data.requestLogId !== void 0) imageMetadata.requestLogId = data.requestLogId;
1239
+ imageMetadata.generation = image.generation;
1240
+ if (image.revisedPrompt !== null && image.revisedPrompt !== void 0) imageMetadata.revisedPrompt = image.revisedPrompt;
1241
+ const result = {
770
1242
  images: [image.data],
771
1243
  warnings,
772
1244
  response: {
773
1245
  timestamp,
774
- modelId: image.generation.model ?? data.model,
1246
+ modelId: image.generation.model ?? data.model ?? modelId,
775
1247
  headers: Object.fromEntries(response.headers)
776
1248
  },
777
- ...usage ? { usage: {
778
- inputTokens: usage.input_tokens,
779
- outputTokens: usage.output_tokens,
780
- totalTokens: usage.total_tokens
781
- } } : {},
782
- providerMetadata: { qgrid: { images: [{
783
- ...data.requestLogId !== void 0 ? { requestLogId: data.requestLogId } : {},
784
- generation: image.generation,
785
- ...image.revisedPrompt !== null && image.revisedPrompt !== void 0 ? { revisedPrompt: image.revisedPrompt } : {}
786
- }] } }
1249
+ providerMetadata: { qgrid: { images: [imageMetadata] } }
1250
+ };
1251
+ if (usage) result.usage = {
1252
+ inputTokens: usage.input_tokens,
1253
+ outputTokens: usage.output_tokens,
1254
+ totalTokens: usage.total_tokens
1255
+ };
1256
+ return result;
1257
+ }
1258
+ };
1259
+ }
1260
+ const GROK_MAX_IMAGES_PER_CALL = 3;
1261
+ /** `providerOptions.xai` for Grok images, field-for-field from `@ai-sdk/xai` `xaiImageModelOptions`. */
1262
+ const xaiImageOptionsSchema = z.object({
1263
+ aspect_ratio: z.string().optional(),
1264
+ output_format: z.string().optional(),
1265
+ sync_mode: z.boolean().optional(),
1266
+ resolution: z.enum([
1267
+ "1k",
1268
+ "1.5k",
1269
+ "2k"
1270
+ ]).optional(),
1271
+ quality: z.enum([
1272
+ "low",
1273
+ "medium",
1274
+ "high",
1275
+ "auto"
1276
+ ]).optional(),
1277
+ user: z.string().optional()
1278
+ });
1279
+ const XAI_IMAGE_HONORED = /* @__PURE__ */ new Set([
1280
+ "aspect_ratio",
1281
+ "output_format",
1282
+ "resolution",
1283
+ "quality"
1284
+ ]);
1285
+ const GROK_OUTPUT_FORMATS = [
1286
+ "png",
1287
+ "jpeg",
1288
+ "webp"
1289
+ ];
1290
+ function isGrokOutputFormat(value) {
1291
+ return GROK_OUTPUT_FORMATS.some((format) => format === value);
1292
+ }
1293
+ const generateImagesOutputSchema = z.object({
1294
+ requestLogId: z.number().optional(),
1295
+ model: z.string(),
1296
+ tokenName: z.string(),
1297
+ images: z.array(z.object({
1298
+ b64: z.string(),
1299
+ mimeType: z.string(),
1300
+ revisedPrompt: z.string().optional()
1301
+ })),
1302
+ usage: z.object({
1303
+ imageCount: z.number(),
1304
+ costInUsdTicks: z.number().optional()
1305
+ }),
1306
+ costUsd: z.number().optional(),
1307
+ costSource: z.enum(["provider", "pricing_table"]).optional()
1308
+ });
1309
+ function createGrokImage(modelId, config) {
1310
+ const label = "qgrid.image";
1311
+ assertGrokModelId(modelId, label);
1312
+ const { serverUrl, projectName } = resolveMediaConfig(config);
1313
+ return {
1314
+ specificationVersion: "v3",
1315
+ provider: QGRID_PROVIDER_NAME,
1316
+ modelId,
1317
+ maxImagesPerCall: GROK_MAX_IMAGES_PER_CALL,
1318
+ async doGenerate(options) {
1319
+ if (!options.prompt?.trim()) throw new Error(`${label}: a non-empty text prompt is required`);
1320
+ if (!Number.isInteger(options.n) || options.n < 1 || options.n > GROK_MAX_IMAGES_PER_CALL) throw new Error(`${label}: Grok returns 1 to ${GROK_MAX_IMAGES_PER_CALL} images per call; omit maxImagesPerCall so AI SDK can batch`);
1321
+ const files = options.files ?? [];
1322
+ if (files.length > 5) throw new Error(`${label}: at most five reference images are supported`);
1323
+ const qgridOptions = parseGrokQgridOptions(options.providerOptions.qgrid, label);
1324
+ const xai = parseXaiMediaOptions(options.providerOptions.xai, xaiImageOptionsSchema, XAI_IMAGE_HONORED, label);
1325
+ const outputFormat = xai.options?.output_format;
1326
+ if (outputFormat !== void 0 && !isGrokOutputFormat(outputFormat)) throw new Error(`${label}: providerOptions.xai.output_format must be png, jpeg, or webp`);
1327
+ const warnings = [];
1328
+ if (options.size !== void 0) warnings.push({
1329
+ type: "unsupported",
1330
+ feature: "size",
1331
+ details: "This model does not support the `size` option. Use `aspectRatio` instead."
1332
+ });
1333
+ if (options.seed !== void 0) warnings.push({
1334
+ type: "unsupported",
1335
+ feature: "seed"
1336
+ });
1337
+ if (options.mask !== void 0) warnings.push({
1338
+ type: "unsupported",
1339
+ feature: "mask"
1340
+ });
1341
+ warnings.push(...xai.warnings);
1342
+ const referenceImages = files.map((file) => toImageRef(file, label));
1343
+ assertInlineMediaFits(referenceImages, label);
1344
+ const aspectRatio = options.aspectRatio ?? xai.options?.aspect_ratio;
1345
+ const params = {
1346
+ model: modelId,
1347
+ prompt: options.prompt,
1348
+ n: options.n
1349
+ };
1350
+ if (aspectRatio !== void 0) params.aspectRatio = aspectRatio;
1351
+ if (xai.options?.resolution !== void 0) params.resolution = xai.options.resolution;
1352
+ if (xai.options?.quality !== void 0) params.quality = xai.options.quality;
1353
+ if (outputFormat !== void 0) params.outputFormat = outputFormat;
1354
+ if (referenceImages.length) params.referenceImages = referenceImages;
1355
+ if (projectName) params.projectName = projectName;
1356
+ const body = serializeMediaBody({
1357
+ ...params,
1358
+ ...qgridOptions
1359
+ }, label);
1360
+ const timestamp = /* @__PURE__ */ new Date();
1361
+ const { data, headers } = await postMediaEndpoint({
1362
+ serverUrl,
1363
+ endpoint: "generateImages",
1364
+ body,
1365
+ schema: generateImagesOutputSchema,
1366
+ label,
1367
+ signal: options.abortSignal,
1368
+ headers: options.headers
1369
+ });
1370
+ if (data.images.length === 0) throw new Error(`${label}: the Grok backend returned no images`);
1371
+ const costInUsdTicks = data.usage.costInUsdTicks;
1372
+ const callCost = {};
1373
+ if (data.costUsd !== void 0) callCost.costUsd = data.costUsd;
1374
+ if (data.costSource !== void 0) callCost.costSource = data.costSource;
1375
+ const xaiImages = data.images.map((image, index) => {
1376
+ const entry = {};
1377
+ if (image.revisedPrompt) entry.revisedPrompt = image.revisedPrompt;
1378
+ if (index === 0 && costInUsdTicks !== void 0) entry.costInUsdTicks = costInUsdTicks;
1379
+ return entry;
1380
+ });
1381
+ const xaiCallCost = {};
1382
+ if (costInUsdTicks !== void 0) xaiCallCost.costInUsdTicks = costInUsdTicks;
1383
+ const qgridImages = data.images.map((image, index) => {
1384
+ const entry = {
1385
+ mediaType: image.mimeType,
1386
+ tokenName: data.tokenName
1387
+ };
1388
+ if (data.requestLogId !== void 0) entry.requestLogId = data.requestLogId;
1389
+ return index === 0 ? {
1390
+ ...entry,
1391
+ ...callCost
1392
+ } : entry;
1393
+ });
1394
+ const qgridCallInfo = {};
1395
+ if (data.requestLogId !== void 0) qgridCallInfo.requestLogId = data.requestLogId;
1396
+ qgridCallInfo.tokenName = data.tokenName;
1397
+ return {
1398
+ images: data.images.map((image) => image.b64),
1399
+ warnings,
1400
+ response: {
1401
+ timestamp,
1402
+ modelId: data.model,
1403
+ headers
1404
+ },
1405
+ providerMetadata: {
1406
+ xai: {
1407
+ images: xaiImages,
1408
+ ...xaiCallCost
1409
+ },
1410
+ qgrid: {
1411
+ images: qgridImages,
1412
+ ...qgridCallInfo,
1413
+ ...callCost
1414
+ }
1415
+ }
1416
+ };
1417
+ }
1418
+ };
1419
+ }
1420
+ //#endregion
1421
+ //#region src/speech-model.ts
1422
+ const LABEL$2 = "qgrid.speech";
1423
+ const MAX_TEXT_CHARS = 15e3;
1424
+ /** Codecs of `@ai-sdk/xai` `XaiSpeechCodec`, equal to the qgrid server's `format` enum. */
1425
+ const SPEECH_FORMATS = [
1426
+ "mp3",
1427
+ "wav",
1428
+ "pcm",
1429
+ "mulaw",
1430
+ "alaw"
1431
+ ];
1432
+ function isSpeechFormat(value) {
1433
+ return SPEECH_FORMATS.some((format) => format === value);
1434
+ }
1435
+ /**
1436
+ * `providerOptions.xai` for Grok speech, field-for-field from `@ai-sdk/xai`
1437
+ * `xaiSpeechModelOptionsSchema`. The qgrid server takes none of them; every set key warns.
1438
+ */
1439
+ const xaiSpeechOptionsSchema = z.object({
1440
+ sampleRate: z.union([
1441
+ z.literal(8e3),
1442
+ z.literal(16e3),
1443
+ z.literal(22050),
1444
+ z.literal(24e3),
1445
+ z.literal(44100),
1446
+ z.literal(48e3)
1447
+ ]).nullish(),
1448
+ bitRate: z.union([
1449
+ z.literal(32e3),
1450
+ z.literal(64e3),
1451
+ z.literal(96e3),
1452
+ z.literal(128e3),
1453
+ z.literal(192e3)
1454
+ ]).nullish(),
1455
+ optimizeStreamingLatency: z.union([
1456
+ z.literal(0),
1457
+ z.literal(1),
1458
+ z.literal(2)
1459
+ ]).nullish(),
1460
+ textNormalization: z.boolean().nullish(),
1461
+ withTimestamps: z.boolean().nullish(),
1462
+ replace: z.record(z.string(), z.string()).nullish()
1463
+ });
1464
+ const speakOutputSchema = z.object({
1465
+ audio: z.string(),
1466
+ mimeType: z.string(),
1467
+ tokenName: z.string(),
1468
+ requestLogId: z.number().optional()
1469
+ });
1470
+ function base64ToBytes(data) {
1471
+ if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(data, "base64"));
1472
+ const binary = atob(data);
1473
+ const bytes = new Uint8Array(binary.length);
1474
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
1475
+ return bytes;
1476
+ }
1477
+ /**
1478
+ * Grok text-to-speech through `POST /api/qgrid/speak`. `voice`, `language` and `outputFormat`
1479
+ * map to the server's `voice`, `language` and `format`; when omitted the server applies
1480
+ * `ara` / `en` / `mp3` (the only combination verified live). The model id only labels the log.
1481
+ */
1482
+ function createQgridSpeech(modelId, config) {
1483
+ assertGrokModelId(modelId, LABEL$2);
1484
+ const { serverUrl, projectName } = resolveMediaConfig(config);
1485
+ return {
1486
+ specificationVersion: "v3",
1487
+ provider: QGRID_PROVIDER_NAME,
1488
+ modelId,
1489
+ async doGenerate(options) {
1490
+ if (!options.text.trim()) throw new Error(`${LABEL$2}: text must not be empty`);
1491
+ if (options.text.length > MAX_TEXT_CHARS) throw new Error(`${LABEL$2}: text is limited to ${MAX_TEXT_CHARS} characters`);
1492
+ const qgridOptions = parseGrokQgridOptions(options.providerOptions?.qgrid, LABEL$2);
1493
+ const xai = parseXaiMediaOptions(options.providerOptions?.xai, xaiSpeechOptionsSchema, /* @__PURE__ */ new Set(), LABEL$2);
1494
+ const warnings = [];
1495
+ let format;
1496
+ if (options.outputFormat !== void 0) if (isSpeechFormat(options.outputFormat)) format = options.outputFormat;
1497
+ else {
1498
+ format = "mp3";
1499
+ warnings.push({
1500
+ type: "unsupported",
1501
+ feature: "outputFormat",
1502
+ details: `Unsupported output format: ${options.outputFormat}. Using mp3 instead.`
1503
+ });
1504
+ }
1505
+ if (options.instructions !== void 0) warnings.push({
1506
+ type: "unsupported",
1507
+ feature: "instructions"
1508
+ });
1509
+ if (options.speed !== void 0) warnings.push({
1510
+ type: "unsupported",
1511
+ feature: "speed"
1512
+ });
1513
+ warnings.push(...xai.warnings);
1514
+ const requestParams = {
1515
+ model: modelId,
1516
+ text: options.text
1517
+ };
1518
+ if (options.voice !== void 0) requestParams.voice = options.voice;
1519
+ if (options.language !== void 0) requestParams.language = options.language;
1520
+ if (format !== void 0) requestParams.format = format;
1521
+ if (projectName) requestParams.projectName = projectName;
1522
+ const params = {
1523
+ ...requestParams,
1524
+ ...qgridOptions
1525
+ };
1526
+ const body = serializeMediaBody(params, LABEL$2);
1527
+ const timestamp = /* @__PURE__ */ new Date();
1528
+ const { data, headers } = await postMediaEndpoint({
1529
+ serverUrl,
1530
+ endpoint: "speak",
1531
+ body,
1532
+ schema: speakOutputSchema,
1533
+ label: LABEL$2,
1534
+ signal: options.abortSignal,
1535
+ headers: options.headers
1536
+ });
1537
+ const qgridMetadata = {
1538
+ mediaType: data.mimeType,
1539
+ tokenName: data.tokenName
1540
+ };
1541
+ if (data.requestLogId !== void 0) qgridMetadata.requestLogId = data.requestLogId;
1542
+ return {
1543
+ audio: base64ToBytes(data.audio),
1544
+ warnings,
1545
+ request: { body: params },
1546
+ response: {
1547
+ timestamp,
1548
+ modelId,
1549
+ headers
1550
+ },
1551
+ providerMetadata: { qgrid: qgridMetadata }
1552
+ };
1553
+ }
1554
+ };
1555
+ }
1556
+ //#endregion
1557
+ //#region src/transcription-model.ts
1558
+ const LABEL$1 = "qgrid.transcription";
1559
+ const AUDIO_MEDIA_TYPE = /^audio\/[A-Za-z0-9.+-]+$/;
1560
+ /**
1561
+ * `providerOptions.xai` for Grok transcription, field-for-field from `@ai-sdk/xai`
1562
+ * `xaiTranscriptionModelOptionsSchema`. Only `language` reaches the qgrid server.
1563
+ */
1564
+ const xaiTranscriptionOptionsSchema = z.object({
1565
+ audioFormat: z.enum([
1566
+ "pcm",
1567
+ "mulaw",
1568
+ "alaw",
1569
+ "opus"
1570
+ ]).nullish(),
1571
+ sampleRate: z.union([
1572
+ z.literal(8e3),
1573
+ z.literal(16e3),
1574
+ z.literal(22050),
1575
+ z.literal(24e3),
1576
+ z.literal(44100),
1577
+ z.literal(48e3)
1578
+ ]).nullish(),
1579
+ language: z.string().min(1).max(16).nullish(),
1580
+ format: z.boolean().nullish(),
1581
+ multichannel: z.boolean().nullish(),
1582
+ channels: z.number().int().min(2).max(8).nullish(),
1583
+ diarize: z.boolean().nullish(),
1584
+ keyterm: z.union([z.string(), z.array(z.string())]).nullish(),
1585
+ fillerWords: z.boolean().nullish(),
1586
+ vadThreshold: z.number().min(0).max(1).optional(),
1587
+ streaming: z.object({}).loose().optional()
1588
+ });
1589
+ const XAI_TRANSCRIPTION_HONORED = /* @__PURE__ */ new Set(["language"]);
1590
+ const transcribeOutputSchema = z.object({
1591
+ text: z.string(),
1592
+ language: z.string().optional(),
1593
+ durationSec: z.number().optional(),
1594
+ model: z.string(),
1595
+ tokenName: z.string(),
1596
+ requestLogId: z.number().optional()
1597
+ });
1598
+ function normalizeMediaType(mediaType) {
1599
+ const bare = mediaType.split(";")[0].trim().toLowerCase();
1600
+ if (!AUDIO_MEDIA_TYPE.test(bare)) throw new Error(`${LABEL$1}: mediaType must be audio/*, got "${mediaType}"`);
1601
+ return bare;
1602
+ }
1603
+ function createQgridTranscription(modelId, config) {
1604
+ assertGrokModelId(modelId, LABEL$1);
1605
+ const { serverUrl, projectName } = resolveMediaConfig(config);
1606
+ return {
1607
+ specificationVersion: "v3",
1608
+ provider: QGRID_PROVIDER_NAME,
1609
+ modelId,
1610
+ async doGenerate(options) {
1611
+ const mediaType = normalizeMediaType(options.mediaType);
1612
+ const audio = options.audio instanceof Uint8Array ? bytesToBase64(options.audio) : options.audio;
1613
+ if (audio.length === 0) throw new Error(`${LABEL$1}: audio is empty`);
1614
+ assertInlineMediaFits([audio], LABEL$1);
1615
+ const qgridOptions = parseGrokQgridOptions(options.providerOptions?.qgrid, LABEL$1);
1616
+ const xai = parseXaiMediaOptions(options.providerOptions?.xai, xaiTranscriptionOptionsSchema, XAI_TRANSCRIPTION_HONORED, LABEL$1);
1617
+ const warnings = [...xai.warnings];
1618
+ const language = xai.options?.language ?? void 0;
1619
+ const params = {
1620
+ model: modelId,
1621
+ audio,
1622
+ mediaType
1623
+ };
1624
+ if (language !== void 0) params.language = language;
1625
+ if (projectName) params.projectName = projectName;
1626
+ const body = serializeMediaBody({
1627
+ ...params,
1628
+ ...qgridOptions
1629
+ }, LABEL$1);
1630
+ const timestamp = /* @__PURE__ */ new Date();
1631
+ const { data, headers } = await postMediaEndpoint({
1632
+ serverUrl,
1633
+ endpoint: "transcribe",
1634
+ body,
1635
+ schema: transcribeOutputSchema,
1636
+ label: LABEL$1,
1637
+ signal: options.abortSignal,
1638
+ headers: options.headers
1639
+ });
1640
+ const qgridMetadata = { tokenName: data.tokenName };
1641
+ if (data.requestLogId !== void 0) qgridMetadata.requestLogId = data.requestLogId;
1642
+ return {
1643
+ text: data.text,
1644
+ segments: [],
1645
+ language: data.language,
1646
+ durationInSeconds: data.durationSec,
1647
+ warnings,
1648
+ response: {
1649
+ timestamp,
1650
+ modelId: data.model,
1651
+ headers
1652
+ },
1653
+ providerMetadata: { qgrid: qgridMetadata }
1654
+ };
1655
+ }
1656
+ };
1657
+ }
1658
+ //#endregion
1659
+ //#region src/video-model.ts
1660
+ const LABEL = "qgrid.video";
1661
+ const MAX_CONSECUTIVE_POLL_FAILURES = 3;
1662
+ const MAX_REFERENCE_IMAGES = 5;
1663
+ const LAST_FRAME_MODEL = "grok/grok-imagine-video-1.5";
1664
+ const RESOLUTION_MAP = /* @__PURE__ */ new Map([
1665
+ ["1920x1080", "1080p"],
1666
+ ["1280x720", "720p"],
1667
+ ["854x480", "480p"],
1668
+ ["640x480", "480p"]
1669
+ ]);
1670
+ /**
1671
+ * `providerOptions.xai` for Grok video. Field names follow `@ai-sdk/xai` `xaiVideoModelOptions`;
1672
+ * `lastFrame` is a qgrid extension because AI SDK v3 video calls have no `frameImages`.
1673
+ * Edit / extend modes need a source video URL the qgrid server does not accept, so they throw.
1674
+ */
1675
+ const xaiVideoOptionsSchema = z.looseObject({
1676
+ mode: z.enum([
1677
+ "edit-video",
1678
+ "extend-video",
1679
+ "reference-to-video"
1680
+ ]).optional(),
1681
+ videoUrl: z.string().min(1).optional(),
1682
+ referenceImageUrls: z.array(z.string().min(1)).min(1).max(7).optional(),
1683
+ referenceVoiceIds: z.array(z.string().min(1)).max(3).optional(),
1684
+ keyframes: z.array(z.unknown()).optional(),
1685
+ storageOptions: z.unknown().optional(),
1686
+ user: z.string().optional(),
1687
+ pollIntervalMs: z.number().positive().nullish(),
1688
+ pollTimeoutMs: z.number().positive().nullish(),
1689
+ resolution: z.enum([
1690
+ "480p",
1691
+ "720p",
1692
+ "1080p"
1693
+ ]).nullish(),
1694
+ lastFrame: z.string().min(1).optional()
1695
+ });
1696
+ const XAI_VIDEO_HONORED = /* @__PURE__ */ new Set([
1697
+ "mode",
1698
+ "referenceImageUrls",
1699
+ "resolution",
1700
+ "lastFrame",
1701
+ "pollIntervalMs",
1702
+ "pollTimeoutMs"
1703
+ ]);
1704
+ const videoStartOutputSchema = z.object({
1705
+ jobHandle: z.string(),
1706
+ model: z.string(),
1707
+ requestLogId: z.number().optional()
1708
+ });
1709
+ const videoStatusOutputSchema = z.object({
1710
+ status: z.enum([
1711
+ "pending",
1712
+ "done",
1713
+ "failed"
1714
+ ]),
1715
+ url: z.string().optional(),
1716
+ mimeType: z.literal("video/mp4"),
1717
+ durationSec: z.number().optional(),
1718
+ costInUsdTicks: z.number().optional(),
1719
+ costUsd: z.number().optional(),
1720
+ error: z.string().optional(),
1721
+ requestLogId: z.number().optional()
1722
+ });
1723
+ /**
1724
+ * Raised when the poll budget runs out. The job keeps running on xAI; pass `jobHandle` to
1725
+ * `resumeQgridVideo` (with the same `projectName`) to keep waiting.
1726
+ */
1727
+ var QgridVideoTimeoutError = class extends Error {
1728
+ jobHandle;
1729
+ constructor(jobHandle, timeoutMs) {
1730
+ super(`${LABEL}: video job ${jobHandle} did not finish within ${timeoutMs} ms; resume it with resumeQgridVideo("${jobHandle}")`);
1731
+ this.name = "QgridVideoTimeoutError";
1732
+ this.jobHandle = jobHandle;
1733
+ }
1734
+ };
1735
+ /** The upstream job finished as failed (including content-policy blocks). */
1736
+ var QgridVideoGenerationError = class extends Error {
1737
+ jobHandle;
1738
+ constructor(jobHandle, detail) {
1739
+ super(`${LABEL}: video generation failed${detail ? `: ${detail}` : "."}`);
1740
+ this.name = "QgridVideoGenerationError";
1741
+ this.jobHandle = jobHandle;
1742
+ }
1743
+ };
1744
+ function abortedError() {
1745
+ return new DOMException("The operation was aborted.", "AbortError");
1746
+ }
1747
+ function sleep(ms, signal) {
1748
+ return new Promise((resolve, reject) => {
1749
+ if (signal?.aborted) {
1750
+ reject(signal.reason ?? abortedError());
1751
+ return;
1752
+ }
1753
+ const onAbort = () => {
1754
+ clearTimeout(timer);
1755
+ reject(signal?.reason ?? abortedError());
1756
+ };
1757
+ const timer = setTimeout(() => {
1758
+ signal?.removeEventListener("abort", onAbort);
1759
+ resolve();
1760
+ }, ms);
1761
+ signal?.addEventListener("abort", onAbort, { once: true });
1762
+ });
1763
+ }
1764
+ function isTransientPollError(cause) {
1765
+ if (cause instanceof QgridMediaHttpError) return cause.status === 429 || cause.status >= 500;
1766
+ return cause instanceof Error && /transport failed/.test(cause.message);
1767
+ }
1768
+ function pollDeadline(caller, ms) {
1769
+ const controller = new AbortController();
1770
+ const onCallerAbort = () => controller.abort(caller?.reason ?? abortedError());
1771
+ if (caller?.aborted) onCallerAbort();
1772
+ else caller?.addEventListener("abort", onCallerAbort, { once: true });
1773
+ const deadlineReason = new DOMException("The video poll budget elapsed.", "TimeoutError");
1774
+ const timer = setTimeout(() => controller.abort(deadlineReason), Math.max(0, ms));
1775
+ return {
1776
+ signal: controller.signal,
1777
+ expired: (cause) => controller.signal.reason === deadlineReason && cause === deadlineReason,
1778
+ dispose: () => {
1779
+ clearTimeout(timer);
1780
+ caller?.removeEventListener("abort", onCallerAbort);
1781
+ }
1782
+ };
1783
+ }
1784
+ async function pollVideo(jobHandle, modelId, config, options, extraMetadata) {
1785
+ const interval = options.pollIntervalMs ?? 5e3;
1786
+ const timeout = options.pollTimeoutMs ?? 3e5;
1787
+ const statusParams = { jobHandle };
1788
+ if (config.projectName) statusParams.projectName = config.projectName;
1789
+ const body = serializeMediaBody(statusParams, LABEL);
1790
+ const deadline = pollDeadline(options.abortSignal, timeout);
1791
+ try {
1792
+ return await pollUntilDone(jobHandle, modelId, config, options, extraMetadata, {
1793
+ body,
1794
+ interval,
1795
+ timeout,
1796
+ signal: deadline.signal
1797
+ });
1798
+ } catch (error) {
1799
+ if (deadline.expired(error)) throw new QgridVideoTimeoutError(jobHandle, timeout);
1800
+ throw error;
1801
+ } finally {
1802
+ deadline.dispose();
1803
+ }
1804
+ }
1805
+ async function pollUntilDone(jobHandle, modelId, config, options, extraMetadata, poll) {
1806
+ const { body, interval, timeout, signal } = poll;
1807
+ const deadline = Date.now() + timeout;
1808
+ let failures = 0;
1809
+ for (;;) {
1810
+ signal.throwIfAborted();
1811
+ let polled;
1812
+ try {
1813
+ polled = await postMediaEndpoint({
1814
+ serverUrl: config.serverUrl,
1815
+ endpoint: "videoStatus",
1816
+ body,
1817
+ schema: videoStatusOutputSchema,
1818
+ label: LABEL,
1819
+ signal,
1820
+ headers: options.headers
1821
+ });
1822
+ failures = 0;
1823
+ } catch (error) {
1824
+ if (!isTransientPollError(error) || ++failures >= MAX_CONSECUTIVE_POLL_FAILURES) throw error;
1825
+ }
1826
+ if (polled) {
1827
+ const status = polled.data;
1828
+ if (status.status === "failed") throw new QgridVideoGenerationError(jobHandle, status.error);
1829
+ if (status.status === "done") {
1830
+ if (!status.url) throw new QgridVideoGenerationError(jobHandle, "completed without a video URL");
1831
+ const requestLogId = status.requestLogId ?? extraMetadata.requestLogId;
1832
+ const xaiMetadata = { videoUrl: status.url };
1833
+ if (status.durationSec !== void 0) xaiMetadata.duration = status.durationSec;
1834
+ if (status.costInUsdTicks !== void 0) xaiMetadata.costInUsdTicks = status.costInUsdTicks;
1835
+ const qgridMetadata = { jobHandle };
1836
+ if (requestLogId !== void 0) qgridMetadata.requestLogId = requestLogId;
1837
+ if (status.costUsd !== void 0) qgridMetadata.costUsd = status.costUsd;
1838
+ return {
1839
+ videos: [{
1840
+ type: "url",
1841
+ url: status.url,
1842
+ mediaType: "video/mp4"
1843
+ }],
1844
+ providerMetadata: {
1845
+ xai: xaiMetadata,
1846
+ qgrid: qgridMetadata
1847
+ },
1848
+ response: {
1849
+ timestamp: /* @__PURE__ */ new Date(),
1850
+ modelId,
1851
+ headers: polled.headers
1852
+ }
1853
+ };
1854
+ }
1855
+ }
1856
+ const remaining = deadline - Date.now();
1857
+ if (remaining <= 0) throw new QgridVideoTimeoutError(jobHandle, timeout);
1858
+ await sleep(Math.min(interval, remaining), signal);
1859
+ }
1860
+ }
1861
+ /**
1862
+ * Keeps polling a video job started by `qgrid.video(...)`, for example after a
1863
+ * `QgridVideoTimeoutError`. `config.projectName` must equal the one used to start the job.
1864
+ * AI SDK v3 video models expose only `doGenerate` (no `doStart` / `doStatus`), so resuming is
1865
+ * a qgrid helper rather than an `experimental_generateVideo` option.
1866
+ */
1867
+ function resumeQgridVideo(jobHandle, options = {}) {
1868
+ const config = resolveMediaConfig(options);
1869
+ return pollVideo(jobHandle, options.modelId ?? "grok/video", config, options, {});
1870
+ }
1871
+ function createQgridVideo(modelId, config) {
1872
+ assertGrokModelId(modelId, LABEL);
1873
+ const resolved = resolveMediaConfig(config);
1874
+ return {
1875
+ specificationVersion: "v3",
1876
+ provider: QGRID_PROVIDER_NAME,
1877
+ modelId,
1878
+ maxVideosPerCall: 1,
1879
+ async doGenerate(options) {
1880
+ if (options.n !== 1) throw new Error(`${LABEL}: one video per call; omit maxVideosPerCall so AI SDK can batch`);
1881
+ if (!options.prompt?.trim()) throw new Error(`${LABEL}: a non-empty text prompt is required`);
1882
+ if (options.duration !== void 0 && (!Number.isInteger(options.duration) || options.duration < 1 || options.duration > 15)) throw new Error(`${LABEL}: duration must be an integer from 1 to 15 seconds`);
1883
+ const qgridOptions = parseGrokQgridOptions(options.providerOptions.qgrid, LABEL);
1884
+ const xai = parseXaiMediaOptions(options.providerOptions.xai, xaiVideoOptionsSchema, XAI_VIDEO_HONORED, LABEL);
1885
+ const xaiOptions = xai.options;
1886
+ if (xaiOptions?.mode === "edit-video" || xaiOptions?.mode === "extend-video" || xaiOptions?.videoUrl !== void 0) throw new Error(`${LABEL}: video editing and extension (mode edit-video / extend-video, videoUrl) are not supported by qgrid`);
1887
+ const referenceImages = (xaiOptions?.referenceImageUrls ?? []).map((url) => toImageRefUrl(url, LABEL));
1888
+ if (referenceImages.length > MAX_REFERENCE_IMAGES) throw new Error(`${LABEL}: at most ${MAX_REFERENCE_IMAGES} reference images are supported`);
1889
+ if (xaiOptions?.mode === "reference-to-video" && referenceImages.length === 0) throw new Error(`${LABEL}: reference-to-video needs providerOptions.xai.referenceImageUrls`);
1890
+ const warnings = [...xai.warnings];
1891
+ if (options.fps !== void 0) warnings.push({
1892
+ type: "unsupported",
1893
+ feature: "fps"
1894
+ });
1895
+ if (options.seed !== void 0) warnings.push({
1896
+ type: "unsupported",
1897
+ feature: "seed"
1898
+ });
1899
+ let resolution = xaiOptions?.resolution ?? void 0;
1900
+ if (resolution === void 0 && options.resolution !== void 0) {
1901
+ resolution = RESOLUTION_MAP.get(options.resolution);
1902
+ if (resolution === void 0) warnings.push({
1903
+ type: "unsupported",
1904
+ feature: "resolution",
1905
+ details: `Unrecognized resolution "${options.resolution}". Use providerOptions.xai.resolution with "480p", "720p", or "1080p" instead.`
1906
+ });
1907
+ }
1908
+ let lastFrame;
1909
+ if (xaiOptions?.lastFrame !== void 0) if (modelId === LAST_FRAME_MODEL) lastFrame = toImageRefUrl(xaiOptions.lastFrame, LABEL);
1910
+ else warnings.push({
1911
+ type: "unsupported",
1912
+ feature: "providerOptions.xai.lastFrame",
1913
+ details: `xAI only supports last_frame with "${LAST_FRAME_MODEL}". The last frame was ignored.`
1914
+ });
1915
+ const image = options.image === void 0 ? void 0 : toImageRef(options.image, LABEL);
1916
+ assertInlineMediaFits([
1917
+ image,
1918
+ lastFrame,
1919
+ ...referenceImages
1920
+ ], LABEL);
1921
+ const params = {
1922
+ model: modelId,
1923
+ prompt: options.prompt
1924
+ };
1925
+ if (options.duration !== void 0) params.duration = options.duration;
1926
+ if (options.aspectRatio !== void 0) params.aspectRatio = options.aspectRatio;
1927
+ if (resolution !== void 0) params.resolution = resolution;
1928
+ if (image !== void 0) params.image = image;
1929
+ if (lastFrame !== void 0) params.lastFrame = lastFrame;
1930
+ if (referenceImages.length) params.referenceImages = referenceImages;
1931
+ if (xaiOptions?.mode !== void 0) params.mode = xaiOptions.mode;
1932
+ if (resolved.projectName) params.projectName = resolved.projectName;
1933
+ const body = serializeMediaBody({
1934
+ ...params,
1935
+ ...qgridOptions
1936
+ }, LABEL);
1937
+ const started = await postMediaEndpoint({
1938
+ serverUrl: resolved.serverUrl,
1939
+ endpoint: "videoStart",
1940
+ body,
1941
+ schema: videoStartOutputSchema,
1942
+ label: LABEL,
1943
+ signal: options.abortSignal,
1944
+ headers: options.headers
1945
+ });
1946
+ const pollOptions = {};
1947
+ if (xaiOptions?.pollIntervalMs) pollOptions.pollIntervalMs = xaiOptions.pollIntervalMs;
1948
+ if (xaiOptions?.pollTimeoutMs) pollOptions.pollTimeoutMs = xaiOptions.pollTimeoutMs;
1949
+ if (options.abortSignal) pollOptions.abortSignal = options.abortSignal;
1950
+ if (options.headers) pollOptions.headers = options.headers;
1951
+ return {
1952
+ ...await pollVideo(started.data.jobHandle, started.data.model, resolved, pollOptions, started.data.requestLogId === void 0 ? {} : { requestLogId: started.data.requestLogId }),
1953
+ warnings
787
1954
  };
788
1955
  }
789
1956
  };
@@ -794,6 +1961,7 @@ const DEFAULT_RUN_KEY = "__qgrid_default_run__";
794
1961
  const DEFAULT_STALE_RUN_TIMEOUT_MS = 1800 * 1e3;
795
1962
  const STALE_RUN_GRACE_MS = 5e3;
796
1963
  const OVERLAPPING_RUN_ERROR = "createQgridLogger received overlapping runs for the same telemetry key. Pass a unique metadata.qgridRunId per AI SDK call or create a fresh logger integration per call.";
1964
+ const qgridRunIdSchema = z.string().min(1);
797
1965
  function isParseableJson(value) {
798
1966
  try {
799
1967
  JSON.parse(value ?? "");
@@ -837,7 +2005,7 @@ function createQgridLogger(config = {}) {
837
2005
  const onLogError = config.onLogError ?? ((e) => console.warn(`[qgrid-logger] ${e.message}`));
838
2006
  const runs = /* @__PURE__ */ new Map();
839
2007
  const starting = /* @__PURE__ */ new Map();
840
- const keyTtl = typeof config.staleRunTimeoutMs === "number" && config.staleRunTimeoutMs > 0 ? config.staleRunTimeoutMs : DEFAULT_STALE_RUN_TIMEOUT_MS;
2008
+ const keyTtl = config.staleRunTimeoutMs !== void 0 && config.staleRunTimeoutMs > 0 ? config.staleRunTimeoutMs : DEFAULT_STALE_RUN_TIMEOUT_MS;
841
2009
  const suppressedRuns = timedKeySet();
842
2010
  const quarantined = timedKeySet();
843
2011
  const finalizeRun = async (runKey, result) => {
@@ -861,7 +2029,7 @@ function createQgridLogger(config = {}) {
861
2029
  }).catch((e) => onLogError(e instanceof Error ? e : new Error(String(e)))));
862
2030
  run.pendingToolCalls = [];
863
2031
  await Promise.allSettled(run.pendingSteps);
864
- await finishRun(serverUrl, {
2032
+ const finishInput = {
865
2033
  requestLogId: run.requestLogId,
866
2034
  status: result.status,
867
2035
  response: result.response,
@@ -872,16 +2040,16 @@ function createQgridLogger(config = {}) {
872
2040
  totalOutputTokens: result.totalUsage?.outputTokens ?? 0,
873
2041
  totalCacheReadTokens: result.totalUsage?.inputTokenDetails?.cacheReadTokens ?? 0,
874
2042
  totalCacheCreationTokens: result.totalUsage?.inputTokenDetails?.cacheWriteTokens ?? 0,
875
- totalDurationMs: Date.now() - run.startTime,
876
- ...run.isStructured && result.status === "succeeded" ? { responseJsonOk: isParseableJson(result.response) } : {},
877
- ...result.errorMessage ? { errorMessage: result.errorMessage } : {}
878
- }).catch((e) => onLogError(e instanceof Error ? e : new Error(String(e))));
2043
+ totalDurationMs: Date.now() - run.startTime
2044
+ };
2045
+ if (run.isStructured && result.status === "succeeded") finishInput.responseJsonOk = isParseableJson(result.response);
2046
+ if (result.errorMessage) finishInput.errorMessage = result.errorMessage;
2047
+ await finishRun(serverUrl, finishInput).catch((e) => onLogError(e instanceof Error ? e : new Error(String(e))));
879
2048
  };
880
2049
  let autoRunIdCounter = 0;
881
- const resolveRunKey = (event) => {
882
- const qgridRunId = event.metadata?.qgridRunId;
883
- if (typeof qgridRunId === "string" && qgridRunId.length > 0) return `qgridRunId:${qgridRunId}`;
884
- if (typeof event.functionId === "string" && event.functionId.length > 0) return `functionId:${event.functionId}`;
2050
+ const resolveRunKey = (qgridRunId, functionId) => {
2051
+ if (qgridRunId !== void 0) return `qgridRunId:${qgridRunId}`;
2052
+ if (functionId) return `functionId:${functionId}`;
885
2053
  return DEFAULT_RUN_KEY;
886
2054
  };
887
2055
  return {
@@ -889,7 +2057,7 @@ function createQgridLogger(config = {}) {
889
2057
  integrations: [{
890
2058
  async onStart(event) {
891
2059
  if (!event.metadata?.qgridRunId && !event.functionId && event.metadata) event.metadata.qgridRunId = `auto-${++autoRunIdCounter}`;
892
- const runKey = resolveRunKey(event);
2060
+ const runKey = resolveRunKey(qgridRunIdSchema.safeParse(event.metadata?.qgridRunId).data, event.functionId);
893
2061
  if (event.model.provider === "qgrid") return;
894
2062
  if (quarantined.has(runKey)) {
895
2063
  onLogError(/* @__PURE__ */ new Error("createQgridLogger: telemetry key is quarantined after overlap"));
@@ -953,12 +2121,12 @@ function createQgridLogger(config = {}) {
953
2121
  return;
954
2122
  }
955
2123
  let watchdogTimeout = DEFAULT_STALE_RUN_TIMEOUT_MS;
956
- if (typeof config.staleRunTimeoutMs === "number") watchdogTimeout = config.staleRunTimeoutMs;
957
- else if (typeof event.timeout === "number" && event.timeout > 0) watchdogTimeout = event.timeout + STALE_RUN_GRACE_MS;
958
- else {
959
- const totalMs = getRecord(event.timeout)?.totalMs;
960
- if (typeof totalMs === "number" && totalMs > 0) watchdogTimeout = totalMs + STALE_RUN_GRACE_MS;
961
- }
2124
+ const timeout = event.timeout;
2125
+ if (config.staleRunTimeoutMs !== void 0) watchdogTimeout = config.staleRunTimeoutMs;
2126
+ else if (timeout instanceof Object) {
2127
+ const totalMs = timeout.totalMs;
2128
+ if (totalMs !== void 0 && totalMs > 0) watchdogTimeout = totalMs + STALE_RUN_GRACE_MS;
2129
+ } else if (timeout !== void 0 && timeout > 0) watchdogTimeout = timeout + STALE_RUN_GRACE_MS;
962
2130
  let watchdog;
963
2131
  if (watchdogTimeout > 0) {
964
2132
  watchdog = setTimeout(() => {
@@ -1004,7 +2172,7 @@ function createQgridLogger(config = {}) {
1004
2172
  },
1005
2173
  onToolCallFinish(event) {
1006
2174
  if (event.model?.provider === "qgrid") return;
1007
- const runKey = resolveRunKey(event);
2175
+ const runKey = resolveRunKey(qgridRunIdSchema.safeParse(event.metadata?.qgridRunId).data, event.functionId);
1008
2176
  if (suppressedRuns.has(runKey) || quarantined.has(runKey)) return;
1009
2177
  const run = runs.get(runKey);
1010
2178
  if (!run) return;
@@ -1012,7 +2180,7 @@ function createQgridLogger(config = {}) {
1012
2180
  },
1013
2181
  onStepFinish(event) {
1014
2182
  if (event.model?.provider === "qgrid") return;
1015
- const runKey = resolveRunKey(event);
2183
+ const runKey = resolveRunKey(qgridRunIdSchema.safeParse(event.metadata?.qgridRunId).data, event.functionId);
1016
2184
  if (suppressedRuns.has(runKey) || quarantined.has(runKey)) return;
1017
2185
  const run = runs.get(runKey);
1018
2186
  if (!run) return;
@@ -1034,9 +2202,9 @@ function createQgridLogger(config = {}) {
1034
2202
  toolArgs: pending.toolArgs,
1035
2203
  modelName: pending.modelName,
1036
2204
  requestedModelName: pending.requestedModelName,
1037
- toolResult: tr && "output" in tr ? safeStringify(tr.output) : void 0,
2205
+ toolResult: tr && "output" in tr ? loggedPayloadText.parse(tr.output) : void 0,
1038
2206
  toolDurationMs: run.toolDurations.get(pending.toolCallId),
1039
- error: te && "error" in te ? safeStringify(te.error) : void 0
2207
+ error: te && "error" in te ? loggedPayloadText.parse(te.error) : void 0
1040
2208
  }).catch((e) => onLogError(e instanceof Error ? e : new Error(String(e)))));
1041
2209
  run.toolDurations.delete(pending.toolCallId);
1042
2210
  } else remainingPending.push(pending);
@@ -1053,7 +2221,7 @@ function createQgridLogger(config = {}) {
1053
2221
  cacheReadTokens: usage.inputTokenDetails?.cacheReadTokens ?? 0,
1054
2222
  cacheCreationTokens: usage.inputTokenDetails?.cacheWriteTokens ?? 0,
1055
2223
  finishReason,
1056
- reasoningText: typeof reasoningText === "string" && reasoningText.length > 0 ? reasoningText : void 0,
2224
+ reasoningText: reasoningText !== void 0 && reasoningText.length > 0 ? reasoningText : void 0,
1057
2225
  reasoningTokens: usage.outputTokenDetails?.reasoningTokens
1058
2226
  }).catch((e) => onLogError(e instanceof Error ? e : new Error(String(e)))));
1059
2227
  const toolCalls = content.filter((p) => p.type === "tool-call");
@@ -1068,12 +2236,12 @@ function createQgridLogger(config = {}) {
1068
2236
  toolCallIndex: i,
1069
2237
  toolCallId: tc.toolCallId,
1070
2238
  toolName: tc.toolName,
1071
- toolArgs: safeStringify(tc.input),
2239
+ toolArgs: loggedPayloadText.parse(tc.input),
1072
2240
  modelName,
1073
2241
  requestedModelName,
1074
- toolResult: tr && "output" in tr ? safeStringify(tr.output) : void 0,
2242
+ toolResult: tr && "output" in tr ? loggedPayloadText.parse(tr.output) : void 0,
1075
2243
  toolDurationMs: run.toolDurations.get(tc.toolCallId),
1076
- error: te && "error" in te ? safeStringify(te.error) : void 0
2244
+ error: te && "error" in te ? loggedPayloadText.parse(te.error) : void 0
1077
2245
  }).catch((e) => onLogError(e instanceof Error ? e : new Error(String(e)))));
1078
2246
  run.toolDurations.delete(tc.toolCallId);
1079
2247
  } else run.pendingToolCalls.push({
@@ -1081,7 +2249,7 @@ function createQgridLogger(config = {}) {
1081
2249
  toolCallIndex: i,
1082
2250
  toolCallId: tc.toolCallId,
1083
2251
  toolName: tc.toolName,
1084
- toolArgs: safeStringify(tc.input),
2252
+ toolArgs: loggedPayloadText.parse(tc.input),
1085
2253
  modelName,
1086
2254
  requestedModelName
1087
2255
  });
@@ -1089,7 +2257,7 @@ function createQgridLogger(config = {}) {
1089
2257
  },
1090
2258
  async onFinish(event) {
1091
2259
  if (event.model?.provider === "qgrid") return;
1092
- const runKey = resolveRunKey(event);
2260
+ const runKey = resolveRunKey(qgridRunIdSchema.safeParse(event.metadata?.qgridRunId).data, event.functionId);
1093
2261
  if (suppressedRuns.has(runKey)) {
1094
2262
  suppressedRuns.remove(runKey);
1095
2263
  return;
@@ -1100,14 +2268,15 @@ function createQgridLogger(config = {}) {
1100
2268
  const status = event.finishReason === "error" ? "error" : "succeeded";
1101
2269
  const requestedModelName = fullModelName(event.model?.provider, event.model?.modelId) ?? run.requestedModelName;
1102
2270
  const modelName = fullModelName(event.model?.provider, event.response?.modelId);
1103
- await finalizeRun(runKey, {
2271
+ const result = {
1104
2272
  status,
1105
2273
  response: event.text,
1106
2274
  totalUsage: event.totalUsage,
1107
2275
  modelName,
1108
- requestedModelName,
1109
- ...status === "error" && "error" in event ? { errorMessage: getErrorMessage(event.error) } : {}
1110
- });
2276
+ requestedModelName
2277
+ };
2278
+ if (status === "error" && "error" in event) result.errorMessage = getErrorMessage(event.error);
2279
+ await finalizeRun(runKey, result);
1111
2280
  }
1112
2281
  }]
1113
2282
  };
@@ -1117,12 +2286,175 @@ function createQgridLogger(config = {}) {
1117
2286
  const threadCoordStore = /* @__PURE__ */ new Map();
1118
2287
  const CACHE_AFFINITY_NAMESPACE = "qgrid-cache-affinity-v1";
1119
2288
  const MAX_THREAD_COORDS = 1e3;
2289
+ function isGrokModel(modelId) {
2290
+ return modelId.startsWith("grok/");
2291
+ }
2292
+ const IMAGE_SIGNATURES = [
2293
+ {
2294
+ mediaType: "image/png",
2295
+ bytes: [
2296
+ 137,
2297
+ 80,
2298
+ 78,
2299
+ 71
2300
+ ]
2301
+ },
2302
+ {
2303
+ mediaType: "image/jpeg",
2304
+ bytes: [
2305
+ 255,
2306
+ 216,
2307
+ 255
2308
+ ]
2309
+ },
2310
+ {
2311
+ mediaType: "image/gif",
2312
+ bytes: [
2313
+ 71,
2314
+ 73,
2315
+ 70,
2316
+ 56
2317
+ ]
2318
+ },
2319
+ {
2320
+ mediaType: "image/webp",
2321
+ bytes: [
2322
+ 82,
2323
+ 73,
2324
+ 70,
2325
+ 70
2326
+ ]
2327
+ }
2328
+ ];
2329
+ function sniffImageMediaType(bytes) {
2330
+ return IMAGE_SIGNATURES.find((signature) => signature.bytes.every((byte, index) => bytes[index] === byte))?.mediaType;
2331
+ }
2332
+ function grokImageUrl(part) {
2333
+ const mediaType = part.mediaType.toLowerCase();
2334
+ if (!mediaType.startsWith("image/")) return void 0;
2335
+ const { data } = part;
2336
+ if (data instanceof URL) return data.protocol === "https:" || data.protocol === "data:" ? data.href : void 0;
2337
+ if (data instanceof Uint8Array) {
2338
+ const resolved = mediaType === "image/*" ? sniffImageMediaType(data) : mediaType;
2339
+ return resolved ? `data:${resolved};base64,${bytesToBase64(data)}` : void 0;
2340
+ }
2341
+ if (/^(https:|data:)/i.test(data)) return data;
2342
+ if (mediaType === "image/*") return void 0;
2343
+ return `data:${mediaType};base64,${data}`;
2344
+ }
2345
+ function grokUserParts(content) {
2346
+ const parts = [];
2347
+ for (const part of content) if (part.type === "text") parts.push({
2348
+ type: "text",
2349
+ text: part.text
2350
+ });
2351
+ else if (part.type === "file") {
2352
+ const url = grokImageUrl(part);
2353
+ if (url !== void 0) parts.push({
2354
+ type: "image",
2355
+ url
2356
+ });
2357
+ }
2358
+ return parts;
2359
+ }
2360
+ function preparePrompt(modelId, messages) {
2361
+ if (!isGrokModel(modelId)) {
2362
+ const { imageUrls, ...prepared } = extractPromptAndHistory(messages, { includeImages: modelId.startsWith("openai/") });
2363
+ assertImageInputFitsJsonTransport(imageUrls);
2364
+ return prepared;
2365
+ }
2366
+ const extracted = extractPromptAndHistory(messages);
2367
+ const userMessages = messages.filter((message) => message.role === "user");
2368
+ const lastMessage = messages.filter((message) => message.role !== "system").at(-1);
2369
+ const promptMessage = lastMessage?.role === "user" ? lastMessage : void 0;
2370
+ const historyUserMessages = promptMessage ? userMessages.slice(0, -1) : userMessages;
2371
+ let keptImageCount = 0;
2372
+ const imageUrls = [];
2373
+ const countImages = (parts) => {
2374
+ for (const part of parts) if (part.type === "image") {
2375
+ keptImageCount++;
2376
+ imageUrls.push(part.url);
2377
+ }
2378
+ };
2379
+ let historyUserIndex = 0;
2380
+ const history = extracted.history.map((item) => {
2381
+ if (!isUserHistoryMessage(item)) return item;
2382
+ const source = historyUserMessages[historyUserIndex++];
2383
+ if (!source) return item;
2384
+ const parts = grokUserParts(source.content);
2385
+ if (!parts.some((part) => part.type === "image")) return item;
2386
+ countImages(parts);
2387
+ return {
2388
+ type: "message",
2389
+ role: "user",
2390
+ content: parts.map((part) => part.type === "text" ? {
2391
+ type: "input_text",
2392
+ text: part.text
2393
+ } : {
2394
+ type: "input_image",
2395
+ image_url: part.url
2396
+ })
2397
+ };
2398
+ });
2399
+ let input;
2400
+ if (promptMessage) {
2401
+ const parts = grokUserParts(promptMessage.content);
2402
+ if (parts.some((part) => part.type === "image")) {
2403
+ countImages(parts);
2404
+ input = parts.map((part) => part.type === "text" ? {
2405
+ type: "text",
2406
+ text: part.text,
2407
+ text_elements: []
2408
+ } : {
2409
+ type: "image",
2410
+ url: part.url
2411
+ });
2412
+ }
2413
+ }
2414
+ assertImageInputFitsJsonTransport(imageUrls);
2415
+ const prepared = {
2416
+ prompt: extracted.prompt,
2417
+ system: extracted.system,
2418
+ history,
2419
+ droppedImageCount: extracted.droppedImageCount - keptImageCount
2420
+ };
2421
+ if (input) prepared.input = input;
2422
+ return prepared;
2423
+ }
2424
+ function isUserHistoryMessage(item) {
2425
+ return item.type === "message" && item.role === "user";
2426
+ }
2427
+ const prepareStreamOutputSchema = z.object({ streamId: z.string() });
2428
+ const streamDeltaSchema = z.object({ text: z.string() });
2429
+ const streamErrorSchema = z.object({ message: z.string() });
2430
+ function resolveXaiOptions(modelId, options) {
2431
+ const raw = options.providerOptions?.xai;
2432
+ if (!isGrokModel(modelId)) return raw === void 0 ? { warnings: [] } : { warnings: [{
2433
+ type: "unsupported",
2434
+ feature: "providerOptions.xai",
2435
+ details: `providerOptions.xai applies only to grok/ models and was ignored for ${modelId}.`
2436
+ }] };
2437
+ const mapped = mapXaiProviderOptions(raw, modelId);
2438
+ const { effort, promptCacheKey } = mapped.request;
2439
+ const resolved = { warnings: mapped.warnings.map((warning) => ({
2440
+ type: "unsupported",
2441
+ feature: `providerOptions.xai.${warning.key}`,
2442
+ details: warning.reason
2443
+ })) };
2444
+ if (effort !== void 0) resolved.effort = effort;
2445
+ if (promptCacheKey !== void 0) resolved.promptCacheKey = promptCacheKey;
2446
+ return resolved;
2447
+ }
2448
+ const xaiResponseMetadataSchema = z.object({ providerMetadata: z.object({ xai: z.object({
2449
+ costInUsdTicks: z.number().optional(),
2450
+ contextWindow: z.number().optional()
2451
+ }) }) });
1120
2452
  function serializeObjectResponseFormat(responseFormat) {
1121
2453
  const rawSchema = responseFormat?.type === "json" ? responseFormat.schema : void 0;
1122
2454
  if (rawSchema === void 0) return void 0;
1123
2455
  const schemaType = rawSchema.type;
1124
2456
  if (schemaType !== "object") {
1125
- console.warn(`[qgrid] responseFormat.schema top-level type is "${schemaType ?? "unknown"}". ${TOP_LEVEL_SCHEMA_WARNING}`);
2457
+ console.warn(`[qgrid] responseFormat.schema top-level type is "${Array.isArray(schemaType) ? schemaType.join(",") : schemaType ?? "unknown"}". ${TOP_LEVEL_SCHEMA_WARNING}`);
1126
2458
  return;
1127
2459
  }
1128
2460
  return JSON.stringify(rawSchema);
@@ -1146,7 +2478,7 @@ function setThreadCoord(sessionKey, coord) {
1146
2478
  });
1147
2479
  if (threadCoordStore.size > MAX_THREAD_COORDS) threadCoordStore.delete(threadCoordStore.keys().next().value);
1148
2480
  }
1149
- function providerWarnings(options, droppedImageCount) {
2481
+ function providerWarnings(options, droppedImageCount, xaiWarnings = []) {
1150
2482
  const warnings = [];
1151
2483
  if (options.fallbackModels !== void 0) warnings.push({
1152
2484
  type: "unsupported",
@@ -1156,9 +2488,9 @@ function providerWarnings(options, droppedImageCount) {
1156
2488
  if (droppedImageCount > 0) warnings.push({
1157
2489
  type: "unsupported",
1158
2490
  feature: "image input",
1159
- details: `${droppedImageCount} image part(s) were ignored. Only user-message images on openai/ models are sent; for image generation or editing, use generateImage with qgrid.image().`
2491
+ details: `${droppedImageCount} image part(s) were ignored. Only user-message images on openai/ and grok/ models are sent; for image generation or editing, use generateImage with qgrid.image().`
1160
2492
  });
1161
- return warnings;
2493
+ return [...warnings, ...xaiWarnings];
1162
2494
  }
1163
2495
  async function deriveCacheAffinityKey(modelId, sessionKey) {
1164
2496
  if (!sessionKey || modelId.startsWith("anthropic/")) return void 0;
@@ -1167,22 +2499,63 @@ async function deriveCacheAffinityKey(modelId, sessionKey) {
1167
2499
  return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
1168
2500
  }
1169
2501
  function qgridProviderMetadata(data) {
1170
- return { qgrid: {
1171
- ...data.requestLogId !== void 0 ? { requestLogId: data.requestLogId } : {},
1172
- model: data.model,
1173
- requestedModel: data.requestedModel ?? data.model,
1174
- modelFallbacks: data.modelFallbacks ?? [],
1175
- tokenName: data.tokenName ?? null,
1176
- durationMs: data.durationMs,
1177
- costUsd: data.costUsd,
1178
- costSource: data.costSource,
1179
- cacheCreation5mInputTokens: data.usage.cache_creation_5m_input_tokens ?? null,
1180
- cacheCreation1hInputTokens: data.usage.cache_creation_1h_input_tokens ?? null
1181
- } };
2502
+ const xai = xaiResponseMetadataSchema.safeParse(data);
2503
+ const metadata = {};
2504
+ if (xai.success) {
2505
+ const { costInUsdTicks, contextWindow } = xai.data.providerMetadata.xai;
2506
+ const xaiMetadata = {};
2507
+ if (costInUsdTicks !== void 0) xaiMetadata.costInUsdTicks = costInUsdTicks;
2508
+ if (contextWindow !== void 0) xaiMetadata.contextWindow = contextWindow;
2509
+ if (Object.keys(xaiMetadata).length > 0) metadata.xai = xaiMetadata;
2510
+ }
2511
+ const qgridMetadata = {};
2512
+ if (data.requestLogId !== void 0) qgridMetadata.requestLogId = data.requestLogId;
2513
+ qgridMetadata.model = data.model;
2514
+ qgridMetadata.requestedModel = data.requestedModel ?? data.model;
2515
+ qgridMetadata.modelFallbacks = data.modelFallbacks ?? [];
2516
+ qgridMetadata.tokenName = data.tokenName ?? null;
2517
+ qgridMetadata.durationMs = data.durationMs;
2518
+ qgridMetadata.costUsd = data.costUsd;
2519
+ qgridMetadata.costSource = data.costSource;
2520
+ qgridMetadata.cacheCreation5mInputTokens = data.usage.cache_creation_5m_input_tokens ?? null;
2521
+ qgridMetadata.cacheCreation1hInputTokens = data.usage.cache_creation_1h_input_tokens ?? null;
2522
+ metadata.qgrid = qgridMetadata;
2523
+ return metadata;
2524
+ }
2525
+ /** JSON callers often pass null for "unset"; these options treated null as absent before. */
2526
+ function nullAsUnset(schema) {
2527
+ return schema.nullish().transform((value) => value ?? void 0);
1182
2528
  }
2529
+ const qgridProviderOptionsSchema = z.looseObject({
2530
+ thinking: z.boolean().optional(),
2531
+ tokenName: z.string().optional(),
2532
+ logger: nullAsUnset(z.boolean()),
2533
+ fallbackModels: nullAsUnset(z.array(z.string())),
2534
+ sessionKey: nullAsUnset(z.string()),
2535
+ effort: nullAsUnset(z.string()),
2536
+ verbosity: nullAsUnset(z.enum([
2537
+ "low",
2538
+ "medium",
2539
+ "high"
2540
+ ])),
2541
+ reasoningSummary: nullAsUnset(z.enum([
2542
+ "auto",
2543
+ "concise",
2544
+ "detailed",
2545
+ "none"
2546
+ ])),
2547
+ serviceTier: nullAsUnset(z.string()),
2548
+ timeoutMs: z.number().optional()
2549
+ });
1183
2550
  function getQgridProviderOptions(options) {
1184
- const qgridOptions = options.providerOptions?.qgrid ?? {};
1185
- if ("imageGeneration" in qgridOptions || "imageGenerationOptions" in qgridOptions) throw new Error("qgrid: imageGeneration and imageGenerationOptions were removed from language-model options. Use generateImage with qgrid.image().");
2551
+ const raw = options.providerOptions?.qgrid ?? {};
2552
+ if ("imageGeneration" in raw || "imageGenerationOptions" in raw) throw new Error("qgrid: imageGeneration and imageGenerationOptions were removed from language-model options. Use generateImage with qgrid.image().");
2553
+ const parsed = qgridProviderOptionsSchema.safeParse(raw);
2554
+ if (!parsed.success) {
2555
+ const issues = parsed.error.issues.map((issue) => `${issue.path.map(String).join(".") || "(root)"}: ${issue.message}`).join("; ");
2556
+ throw new Error(`qgrid: invalid providerOptions.qgrid: ${issues}`);
2557
+ }
2558
+ const qgridOptions = parsed.data;
1186
2559
  if (qgridOptions.tokenName !== void 0 && qgridOptions.tokenName.trim() === "") throw new Error("qgrid: tokenName must not be empty");
1187
2560
  return qgridOptions;
1188
2561
  }
@@ -1206,7 +2579,7 @@ function toAiSdkUsage(usage) {
1206
2579
  }
1207
2580
  function qgrid(modelId, config) {
1208
2581
  const serverUrl = config?.serverUrl ?? process.env.QGRID_URL ?? "http://localhost:44900";
1209
- const effort = config?.defaultEffort ?? "low";
2582
+ const effort = config?.defaultEffort ?? (isGrokModel(modelId) ? void 0 : "low");
1210
2583
  const projectName = config?.projectName ?? process.env.QGRID_PROJECT_NAME;
1211
2584
  const clientRuns = /* @__PURE__ */ new Map();
1212
2585
  let anonymousClientRunId = 0;
@@ -1250,17 +2623,16 @@ function qgrid(modelId, config) {
1250
2623
  const tools = options.tools?.filter((t) => t.type === "function");
1251
2624
  const hasTools = tools && tools.length > 0;
1252
2625
  const qgridOptions = getQgridProviderOptions(options);
1253
- const { prompt, system, history, input, imageUrls, droppedImageCount } = extractPromptAndHistory(options.prompt, { includeImages: modelId.startsWith("openai/") });
1254
- assertImageInputFitsJsonTransport(imageUrls);
1255
- const effectiveEffort = qgridOptions.thinking === false ? void 0 : qgridOptions.effort ?? effort;
2626
+ const xaiOptions = resolveXaiOptions(modelId, options);
2627
+ const { prompt, system, history, input, droppedImageCount } = preparePrompt(modelId, options.prompt);
2628
+ const effectiveEffort = qgridOptions.thinking === false ? void 0 : qgridOptions.effort ?? xaiOptions.effort ?? effort;
1256
2629
  const verbosity = qgridOptions.verbosity;
1257
2630
  const reasoningSummary = qgridOptions.reasoningSummary;
1258
2631
  const serviceTier = qgridOptions.serviceTier;
1259
2632
  const timeoutMs = qgridOptions.timeoutMs;
1260
2633
  const logger = qgridOptions.logger;
1261
2634
  const tokenName = qgridOptions.tokenName;
1262
- const sessionKey = qgridOptions.sessionKey;
1263
- const cacheAffinityKey = await deriveCacheAffinityKey(modelId, sessionKey);
2635
+ const cacheAffinityKey = await deriveCacheAffinityKey(modelId, qgridOptions.sessionKey ?? xaiOptions.promptCacheKey);
1264
2636
  const jsonSchema = serializeObjectResponseFormat(options.responseFormat);
1265
2637
  let runContext;
1266
2638
  let toolResultsPayload;
@@ -1272,33 +2644,35 @@ function qgrid(modelId, config) {
1272
2644
  const storedCoord = cacheAffinityKey ? getThreadCoord(cacheAffinityKey) : void 0;
1273
2645
  if (!matchedClientRun && storedCoord) runContext = { threadCoord: storedCoord };
1274
2646
  const transport = createQgridRequestTransport(modelId, timeoutMs);
1275
- const data = await qgridFetch(`${serverUrl}/api/qgrid/query`, {
2647
+ const args = {
2648
+ prompt,
2649
+ model: modelId,
2650
+ system,
2651
+ effort: effectiveEffort,
2652
+ thinking: qgridOptions.thinking
2653
+ };
2654
+ if (input) args.input = input;
2655
+ if (verbosity) args.verbosity = verbosity;
2656
+ if (reasoningSummary) args.reasoningSummary = reasoningSummary;
2657
+ if (serviceTier) args.serviceTier = serviceTier;
2658
+ if (timeoutMs !== void 0) args.timeout = timeoutMs;
2659
+ if (hasTools) args.tools = tools.map(toQgridTool);
2660
+ if (jsonSchema) args.jsonSchema = jsonSchema;
2661
+ if (history.length > 0) args.history = JSON.stringify(history);
2662
+ if (projectName) args.projectName = projectName;
2663
+ if (cacheAffinityKey) args.cacheAffinityKey = cacheAffinityKey;
2664
+ if (logger === false) args.logger = false;
2665
+ if (tokenName) args.tokenName = tokenName;
2666
+ if (runContext) args.runContext = runContext;
2667
+ if (toolResultsPayload) args.toolResults = toolResultsPayload;
2668
+ const init = {
1276
2669
  method: "POST",
1277
2670
  headers: { "Content-Type": "application/json" },
1278
- body: JSON.stringify({ args: {
1279
- prompt,
1280
- ...input ? { input } : {},
1281
- model: modelId,
1282
- system,
1283
- effort: effectiveEffort,
1284
- thinking: qgridOptions.thinking,
1285
- ...verbosity ? { verbosity } : {},
1286
- ...reasoningSummary ? { reasoningSummary } : {},
1287
- ...serviceTier ? { serviceTier } : {},
1288
- ...timeoutMs !== void 0 ? { timeout: timeoutMs } : {},
1289
- ...hasTools ? { tools: tools.map(toQgridTool) } : {},
1290
- ...jsonSchema ? { jsonSchema } : {},
1291
- ...history.length > 0 ? { history: JSON.stringify(history) } : {},
1292
- ...projectName ? { projectName } : {},
1293
- ...cacheAffinityKey ? { cacheAffinityKey } : {},
1294
- ...logger === false ? { logger: false } : {},
1295
- ...tokenName ? { tokenName } : {},
1296
- ...runContext ? { runContext } : {},
1297
- ...toolResultsPayload ? { toolResults: toolResultsPayload } : {}
1298
- } }),
1299
- signal: options.abortSignal,
1300
- ...transport ? { dispatcher: transport.dispatcher } : {}
1301
- }, {
2671
+ body: JSON.stringify({ args }),
2672
+ signal: options.abortSignal
2673
+ };
2674
+ if (transport) init.dispatcher = transport.dispatcher;
2675
+ const data = await qgridFetch(`${serverUrl}/api/qgrid/query`, init, {
1302
2676
  operation: "query",
1303
2677
  serverUrl,
1304
2678
  transportTimeoutMs: transport?.timeoutMs
@@ -1307,7 +2681,7 @@ function qgrid(modelId, config) {
1307
2681
  const text = await res.text().catch(() => "");
1308
2682
  throw new Error(`qgrid ${res.status}: ${text}`);
1309
2683
  }
1310
- return await res.json();
2684
+ return queryOutputSchema.parse(await res.json());
1311
2685
  }).finally(() => closeQgridRequestTransport(transport));
1312
2686
  const content = [];
1313
2687
  let finishReason = {
@@ -1334,13 +2708,13 @@ function qgrid(modelId, config) {
1334
2708
  text: data.text
1335
2709
  });
1336
2710
  if (matchedClientRun) clientRuns.delete(matchedClientRun.key);
1337
- if (finishReason.unified === "tool-calls") rememberClientRun(data.runContext, content.filter((c) => c.type === "tool-call").map((c) => c.toolCallId));
2711
+ if (finishReason.unified === "tool-calls") rememberClientRun(data.runContext, content.flatMap((c) => c.type === "tool-call" ? [c.toolCallId] : []));
1338
2712
  if (cacheAffinityKey && data.runContext?.threadCoord) setThreadCoord(cacheAffinityKey, data.runContext.threadCoord);
1339
2713
  return {
1340
2714
  content,
1341
2715
  finishReason,
1342
2716
  usage: toAiSdkUsage(data.usage),
1343
- warnings: providerWarnings(qgridOptions, droppedImageCount),
2717
+ warnings: providerWarnings(qgridOptions, droppedImageCount, xaiOptions.warnings),
1344
2718
  providerMetadata: qgridProviderMetadata(data),
1345
2719
  response: { modelId: data.model }
1346
2720
  };
@@ -1349,17 +2723,16 @@ function qgrid(modelId, config) {
1349
2723
  const tools = options.tools?.filter((t) => t.type === "function");
1350
2724
  const hasTools = tools && tools.length > 0;
1351
2725
  const qgridOptions = getQgridProviderOptions(options);
1352
- const effectiveEffort = qgridOptions.thinking === false ? void 0 : qgridOptions.effort ?? effort;
2726
+ const xaiOptions = resolveXaiOptions(modelId, options);
2727
+ const effectiveEffort = qgridOptions.thinking === false ? void 0 : qgridOptions.effort ?? xaiOptions.effort ?? effort;
1353
2728
  const verbosity = qgridOptions.verbosity;
1354
2729
  const reasoningSummary = qgridOptions.reasoningSummary;
1355
2730
  const serviceTier = qgridOptions.serviceTier;
1356
2731
  const timeoutMs = qgridOptions.timeoutMs;
1357
2732
  const logger = qgridOptions.logger;
1358
2733
  const tokenName = qgridOptions.tokenName;
1359
- const { prompt, system, history, input, imageUrls, droppedImageCount } = extractPromptAndHistory(options.prompt, { includeImages: modelId.startsWith("openai/") });
1360
- assertImageInputFitsJsonTransport(imageUrls);
1361
- const sessionKey = qgridOptions.sessionKey;
1362
- const cacheAffinityKey = await deriveCacheAffinityKey(modelId, sessionKey);
2734
+ const { prompt, system, history, input, droppedImageCount } = preparePrompt(modelId, options.prompt);
2735
+ const cacheAffinityKey = await deriveCacheAffinityKey(modelId, qgridOptions.sessionKey ?? xaiOptions.promptCacheKey);
1363
2736
  const jsonSchema = serializeObjectResponseFormat(options.responseFormat);
1364
2737
  let runContext;
1365
2738
  let toolResultsPayload;
@@ -1372,37 +2745,38 @@ function qgrid(modelId, config) {
1372
2745
  if (!matchedClientRun && storedCoord) runContext = { threadCoord: storedCoord };
1373
2746
  const abortController = new AbortController();
1374
2747
  const signal = options.abortSignal ? AbortSignal.any([options.abortSignal, abortController.signal]) : abortController.signal;
2748
+ const args = {
2749
+ prompt,
2750
+ model: modelId,
2751
+ system,
2752
+ effort: effectiveEffort,
2753
+ thinking: qgridOptions.thinking
2754
+ };
2755
+ if (input) args.input = input;
2756
+ if (verbosity) args.verbosity = verbosity;
2757
+ if (reasoningSummary) args.reasoningSummary = reasoningSummary;
2758
+ if (serviceTier) args.serviceTier = serviceTier;
2759
+ if (timeoutMs !== void 0) args.timeout = timeoutMs;
2760
+ if (hasTools) args.tools = tools.map(toQgridTool);
2761
+ if (jsonSchema) args.jsonSchema = jsonSchema;
2762
+ if (history.length > 0) args.history = JSON.stringify(history);
2763
+ if (projectName) args.projectName = projectName;
2764
+ if (cacheAffinityKey) args.cacheAffinityKey = cacheAffinityKey;
2765
+ if (logger === false) args.logger = false;
2766
+ if (tokenName) args.tokenName = tokenName;
2767
+ if (runContext) args.runContext = runContext;
2768
+ if (toolResultsPayload) args.toolResults = toolResultsPayload;
1375
2769
  const prepRes = await fetch(`${serverUrl}/api/qgrid/prepareStream`, {
1376
2770
  method: "POST",
1377
2771
  headers: { "Content-Type": "application/json" },
1378
- body: JSON.stringify({ args: {
1379
- prompt,
1380
- ...input ? { input } : {},
1381
- model: modelId,
1382
- system,
1383
- effort: effectiveEffort,
1384
- thinking: qgridOptions.thinking,
1385
- ...verbosity ? { verbosity } : {},
1386
- ...reasoningSummary ? { reasoningSummary } : {},
1387
- ...serviceTier ? { serviceTier } : {},
1388
- ...timeoutMs !== void 0 ? { timeout: timeoutMs } : {},
1389
- ...hasTools ? { tools: tools.map(toQgridTool) } : {},
1390
- ...jsonSchema ? { jsonSchema } : {},
1391
- ...history.length > 0 ? { history: JSON.stringify(history) } : {},
1392
- ...projectName ? { projectName } : {},
1393
- ...cacheAffinityKey ? { cacheAffinityKey } : {},
1394
- ...logger === false ? { logger: false } : {},
1395
- ...tokenName ? { tokenName } : {},
1396
- ...runContext ? { runContext } : {},
1397
- ...toolResultsPayload ? { toolResults: toolResultsPayload } : {}
1398
- } }),
2772
+ body: JSON.stringify({ args }),
1399
2773
  signal
1400
2774
  });
1401
2775
  if (!prepRes.ok) {
1402
2776
  const text = await prepRes.text().catch(() => "");
1403
2777
  throw new Error(`qgrid prepareStream ${prepRes.status}: ${text}`);
1404
2778
  }
1405
- const { streamId } = await prepRes.json();
2779
+ const { streamId } = prepareStreamOutputSchema.parse(await prepRes.json());
1406
2780
  const streamRes = await fetch(`${serverUrl}/api/qgrid/queryStream?streamId=${streamId}`, { signal });
1407
2781
  if (!streamRes.ok || !streamRes.body) {
1408
2782
  const text = await streamRes.text().catch(() => "");
@@ -1428,14 +2802,14 @@ function qgrid(modelId, config) {
1428
2802
  try {
1429
2803
  controller.enqueue({
1430
2804
  type: "stream-start",
1431
- warnings: providerWarnings(qgridOptions, droppedImageCount)
2805
+ warnings: providerWarnings(qgridOptions, droppedImageCount, xaiOptions.warnings)
1432
2806
  });
1433
2807
  let streamCompleted = false;
1434
2808
  for await (const event of parseSSE(streamRes.body, signal)) {
1435
2809
  if (cancelled) return;
1436
2810
  signal.throwIfAborted();
1437
2811
  if (event.type === "delta") {
1438
- const raw = event.data.text;
2812
+ const raw = streamDeltaSchema.parse(event.data).text;
1439
2813
  const text = envelopeParser ? envelopeParser.push(raw) : raw;
1440
2814
  if (text !== "") {
1441
2815
  if (!textStarted) {
@@ -1460,7 +2834,7 @@ function qgrid(modelId, config) {
1460
2834
  });
1461
2835
  textStarted = false;
1462
2836
  }
1463
- const done = event.data;
2837
+ const done = queryOutputSchema.parse(event.data);
1464
2838
  if (matchedClientRun) clientRuns.delete(matchedClientRun.key);
1465
2839
  if (done.finishReason === "tool-calls") rememberClientRun(done.runContext, (done.content ?? []).filter((c) => c.type === "tool-call").map((c) => c.toolCallId));
1466
2840
  if (cacheAffinityKey && done.runContext?.threadCoord) setThreadCoord(cacheAffinityKey, done.runContext.threadCoord);
@@ -1524,7 +2898,8 @@ function qgrid(modelId, config) {
1524
2898
  return;
1525
2899
  } else if (event.type === "error") {
1526
2900
  streamCompleted = true;
1527
- controller.error(new Error(event.data.message));
2901
+ const streamError = streamErrorSchema.safeParse(event.data);
2902
+ controller.error(new Error(streamError.success ? streamError.data.message : JSON.stringify(event.data)));
1528
2903
  return;
1529
2904
  }
1530
2905
  }
@@ -1541,5 +2916,8 @@ function qgrid(modelId, config) {
1541
2916
  };
1542
2917
  }
1543
2918
  qgrid.image = createQgridImage;
2919
+ qgrid.video = createQgridVideo;
2920
+ qgrid.transcription = createQgridTranscription;
2921
+ qgrid.speech = createQgridSpeech;
1544
2922
  //#endregion
1545
- export { createQgridLogger, qgrid as default, qgrid };
2923
+ export { QGRID_MAX_REQUEST_BODY_BYTES, QgridMediaHttpError, QgridVideoGenerationError, QgridVideoTimeoutError, QgridXaiOptionsError, createQgridLogger, qgrid as default, qgrid, resumeQgridVideo };