@jacobbd/relay-ai 0.9.1 → 0.9.3

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.
@@ -50,7 +50,7 @@ import { join as join2 } from "path";
50
50
  // package.json
51
51
  var package_default = {
52
52
  name: "@jacobbd/relay-ai",
53
- version: "0.9.1",
53
+ version: "0.9.3",
54
54
  publishConfig: {
55
55
  access: "public"
56
56
  },
@@ -288,7 +288,320 @@ async function refreshOpenAiAccessToken(refreshToken) {
288
288
 
289
289
  // src/oauth/responses-websocket.ts
290
290
  var RESPONSES_LITE_HEADER = "x-openai-internal-codex-responses-lite";
291
- var TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set(["response.completed", "response.failed", "response.incomplete"]);
291
+ var TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set(["response.completed", "response.failed", "response.incomplete", "error"]);
292
+ function isRecord(value) {
293
+ return !!value && typeof value === "object" && !Array.isArray(value);
294
+ }
295
+ function recordKeys(value) {
296
+ return isRecord(value) ? Object.keys(value).sort().join(",") : "";
297
+ }
298
+ function summarizeResponsesLiteEvent(event) {
299
+ if (!isRecord(event)) return `kind=${event == null ? "null" : typeof event}`;
300
+ const parts = [`type=${typeof event.type === "string" ? event.type : "unknown"}`, `keys=${recordKeys(event)}`];
301
+ if (typeof event.delta === "string") parts.push(`deltaChars=${event.delta.length}`);
302
+ if (typeof event.output_index === "number") parts.push(`hasOutputIndex=1`);
303
+ if (typeof event.item_id === "string") parts.push(`hasItemId=1`);
304
+ if (isRecord(event.item)) {
305
+ parts.push(`itemType=${typeof event.item.type === "string" ? event.item.type : "unknown"}`);
306
+ parts.push(`itemKeys=${recordKeys(event.item)}`);
307
+ if (typeof event.item.arguments === "string") parts.push(`argumentsChars=${event.item.arguments.length}`);
308
+ }
309
+ if (isRecord(event.response)) {
310
+ parts.push(`responseKeys=${recordKeys(event.response)}`);
311
+ if (Array.isArray(event.response.output)) {
312
+ parts.push(`outputCount=${event.response.output.length}`);
313
+ parts.push(`outputTypes=${event.response.output.map((item) => isRecord(item) && typeof item.type === "string" ? item.type : "unknown").join(",")}`);
314
+ }
315
+ if (isRecord(event.response.usage)) parts.push(`usageKeys=${recordKeys(event.response.usage)}`);
316
+ if (typeof event.response.status === "string") parts.push(`status=${event.response.status}`);
317
+ }
318
+ if (isRecord(event.error)) {
319
+ parts.push(`errorKeys=${recordKeys(event.error)}`);
320
+ if (typeof event.error.message === "string") parts.push(`messageChars=${event.error.message.length}`);
321
+ }
322
+ return parts.join(" ");
323
+ }
324
+ function createResponsesLiteNormalizeState() {
325
+ return {
326
+ nextId: 1,
327
+ lastOutputIndex: 0,
328
+ textDeltaForwarded: false,
329
+ messageAddedIds: /* @__PURE__ */ new Set(),
330
+ messageDoneIds: /* @__PURE__ */ new Set(),
331
+ functionCalls: []
332
+ };
333
+ }
334
+ function nextId(state, prefix) {
335
+ const id = `${prefix}_${state.nextId}`;
336
+ state.nextId += 1;
337
+ return id;
338
+ }
339
+ function asString(value) {
340
+ return typeof value === "string" && value.length > 0 ? value : void 0;
341
+ }
342
+ function normalizeErrorEvent(event) {
343
+ const raw = isRecord(event.error) ? event.error : { message: typeof event.error === "string" ? event.error : "upstream error" };
344
+ return {
345
+ type: "error",
346
+ sequence_number: typeof event.sequence_number === "number" ? event.sequence_number : 0,
347
+ error: {
348
+ type: asString(raw.type) ?? "server_error",
349
+ code: asString(raw.code) ?? "unknown",
350
+ message: asString(raw.message) ?? "upstream error",
351
+ ...raw.param == null ? {} : { param: raw.param }
352
+ }
353
+ };
354
+ }
355
+ function resolveFunctionCall(state, hint) {
356
+ if (hint.callId) {
357
+ const byCallId = state.functionCalls.find((entry2) => entry2.callId === hint.callId);
358
+ if (byCallId) return byCallId;
359
+ }
360
+ if (hint.itemId) {
361
+ const byItemId = state.functionCalls.find((entry2) => entry2.itemId === hint.itemId);
362
+ if (byItemId) return byItemId;
363
+ }
364
+ if (hint.outputIndex !== void 0) {
365
+ const open3 = [...state.functionCalls].reverse().find((entry2) => entry2.outputIndex === hint.outputIndex && !entry2.done && !(hint.callId && entry2.callId !== hint.callId));
366
+ if (open3) return open3;
367
+ }
368
+ if (hint.callId === void 0 && hint.itemId === void 0 && hint.outputIndex === void 0 && state.lastFunctionCall) {
369
+ return state.lastFunctionCall;
370
+ }
371
+ const entry = {
372
+ itemId: hint.itemId ?? nextId(state, "fc"),
373
+ callId: hint.callId ?? hint.itemId ?? nextId(state, "call"),
374
+ name: "",
375
+ args: "",
376
+ upstream: {},
377
+ outputIndex: hint.outputIndex ?? state.lastOutputIndex,
378
+ added: false,
379
+ deltaForwarded: false,
380
+ doneSeen: false,
381
+ done: false
382
+ };
383
+ state.functionCalls.push(entry);
384
+ return entry;
385
+ }
386
+ function absorbFunctionItem(entry, item, authoritative) {
387
+ entry.upstream = { ...entry.upstream, ...item };
388
+ const name = asString(item.name);
389
+ if (name) entry.name = name;
390
+ const callId = asString(item.call_id);
391
+ if (callId) entry.callId = callId;
392
+ if (authoritative && typeof item.arguments === "string") entry.upstreamArgs = item.arguments;
393
+ }
394
+ function resolveFunctionArgs(entry) {
395
+ if (entry.upstreamArgs) return entry.upstreamArgs;
396
+ if (entry.args) return entry.args;
397
+ return entry.upstreamArgs;
398
+ }
399
+ function functionItemPayload(entry, extra) {
400
+ return {
401
+ ...entry.upstream,
402
+ type: "function_call",
403
+ id: entry.itemId,
404
+ call_id: entry.callId,
405
+ name: entry.name,
406
+ ...extra
407
+ };
408
+ }
409
+ function functionAddedEvent(entry) {
410
+ entry.added = true;
411
+ return {
412
+ type: "response.output_item.added",
413
+ output_index: entry.outputIndex,
414
+ item: functionItemPayload(entry, { arguments: "" })
415
+ };
416
+ }
417
+ function functionDoneEvent(entry, args) {
418
+ entry.done = true;
419
+ return {
420
+ type: "response.output_item.done",
421
+ output_index: entry.outputIndex,
422
+ item: functionItemPayload(entry, { arguments: args, status: "completed" })
423
+ };
424
+ }
425
+ function completeFunctionCall(entry, args) {
426
+ if (entry.done) return [];
427
+ const events = [];
428
+ if (!entry.added) events.push(functionAddedEvent(entry));
429
+ if (!entry.deltaForwarded && args.length > 0) {
430
+ entry.deltaForwarded = true;
431
+ events.push({
432
+ type: "response.function_call_arguments.delta",
433
+ item_id: entry.itemId,
434
+ output_index: entry.outputIndex,
435
+ delta: args
436
+ });
437
+ }
438
+ events.push(functionDoneEvent(entry, args));
439
+ return events;
440
+ }
441
+ function messageText(item) {
442
+ if (typeof item.text === "string") return item.text;
443
+ if (!Array.isArray(item.content)) return "";
444
+ let out = "";
445
+ for (const part of item.content) {
446
+ if (isRecord(part) && typeof part.text === "string" && (part.type === "output_text" || part.type === "text")) {
447
+ out += part.text;
448
+ }
449
+ }
450
+ return out;
451
+ }
452
+ function synthesizeMessage(item, outputIndex, state) {
453
+ const text = messageText(item);
454
+ if (!text) return [];
455
+ const id = asString(item.id) ?? nextId(state, "msg");
456
+ state.lastMessageItemId = id;
457
+ state.textDeltaForwarded = true;
458
+ state.messageAddedIds.add(id);
459
+ state.messageDoneIds.add(id);
460
+ return [
461
+ { type: "response.output_item.added", output_index: outputIndex, item: { type: "message", id } },
462
+ { type: "response.output_text.delta", item_id: id, delta: text },
463
+ { type: "response.output_item.done", output_index: outputIndex, item: { type: "message", id } }
464
+ ];
465
+ }
466
+ function synthesizeFunctionCall(item, outputIndex, state) {
467
+ const entry = resolveFunctionCall(state, {
468
+ itemId: asString(item.id),
469
+ callId: asString(item.call_id),
470
+ outputIndex
471
+ });
472
+ absorbFunctionItem(entry, item, true);
473
+ state.lastFunctionCall = entry;
474
+ state.lastOutputIndex = entry.outputIndex;
475
+ const args = resolveFunctionArgs(entry);
476
+ if (args === void 0) {
477
+ entry.doneSeen = true;
478
+ return [];
479
+ }
480
+ return completeFunctionCall(entry, args);
481
+ }
482
+ function recoverFromCompletedOutput(response, state) {
483
+ const recovered = [];
484
+ if (Array.isArray(response.output)) {
485
+ response.output.forEach((item, index) => {
486
+ if (!isRecord(item) || typeof item.type !== "string") return;
487
+ if (item.type === "message" && !state.textDeltaForwarded) {
488
+ recovered.push(...synthesizeMessage(item, index, state));
489
+ } else if (item.type === "function_call") {
490
+ recovered.push(...synthesizeFunctionCall(item, index, state));
491
+ }
492
+ });
493
+ }
494
+ for (const entry of state.functionCalls) {
495
+ if (entry.done || !entry.doneSeen) continue;
496
+ recovered.push(normalizeErrorEvent({
497
+ error: {
498
+ type: "invalid_response",
499
+ code: "incomplete_function_call",
500
+ message: `Provider ended the response without arguments for function call "${entry.callId}"${entry.name ? ` (${entry.name})` : ""}.`
501
+ }
502
+ }));
503
+ }
504
+ return recovered;
505
+ }
506
+ function normalizeResponsesLiteEvent(event, state) {
507
+ if (!isRecord(event) || typeof event.type !== "string") return [event];
508
+ if (event.type === "error") return [normalizeErrorEvent(event)];
509
+ if (event.type === "response.output_item.added" && isRecord(event.item)) {
510
+ const outputIndex = typeof event.output_index === "number" ? event.output_index : state.lastOutputIndex;
511
+ state.lastOutputIndex = outputIndex;
512
+ if (event.item.type === "message") {
513
+ const id = asString(event.item.id) ?? nextId(state, "msg");
514
+ state.lastMessageItemId = id;
515
+ state.messageAddedIds.add(id);
516
+ return [{ ...event, output_index: outputIndex, item: { ...event.item, id } }];
517
+ }
518
+ if (event.item.type === "function_call") {
519
+ const entry = resolveFunctionCall(state, {
520
+ itemId: asString(event.item.id),
521
+ callId: asString(event.item.call_id),
522
+ outputIndex
523
+ });
524
+ absorbFunctionItem(entry, event.item, false);
525
+ entry.outputIndex = outputIndex;
526
+ entry.added = true;
527
+ state.lastFunctionCall = entry;
528
+ return [{ ...event, output_index: outputIndex, item: functionItemPayload(entry, { arguments: "" }) }];
529
+ }
530
+ return [{ ...event, output_index: outputIndex }];
531
+ }
532
+ if (event.type === "response.output_item.done" && isRecord(event.item)) {
533
+ const outputIndex = typeof event.output_index === "number" ? event.output_index : state.lastOutputIndex;
534
+ if (event.item.type === "function_call") {
535
+ const entry = resolveFunctionCall(state, {
536
+ itemId: asString(event.item.id),
537
+ callId: asString(event.item.call_id),
538
+ outputIndex
539
+ });
540
+ absorbFunctionItem(entry, event.item, true);
541
+ entry.outputIndex = outputIndex;
542
+ entry.doneSeen = true;
543
+ state.lastFunctionCall = entry;
544
+ state.lastOutputIndex = outputIndex;
545
+ const args = resolveFunctionArgs(entry);
546
+ if (args === void 0 || !entry.name) return [];
547
+ if (entry.done) return [];
548
+ const events = [];
549
+ if (!entry.added) events.push(functionAddedEvent(entry));
550
+ events.push({ ...event, output_index: outputIndex, item: functionItemPayload(entry, { arguments: args, status: "completed" }) });
551
+ entry.done = true;
552
+ return events;
553
+ }
554
+ if (event.item.type === "message") {
555
+ const id = asString(event.item.id) ?? state.lastMessageItemId ?? nextId(state, "msg");
556
+ state.lastMessageItemId = id;
557
+ state.messageDoneIds.add(id);
558
+ return [{ ...event, output_index: outputIndex, item: { ...event.item, id } }];
559
+ }
560
+ return [{ ...event, output_index: outputIndex }];
561
+ }
562
+ if (event.type === "response.output_text.delta") {
563
+ const itemId = asString(event.item_id) ?? state.lastMessageItemId ?? nextId(state, "msg");
564
+ state.lastMessageItemId = itemId;
565
+ state.textDeltaForwarded = true;
566
+ const events = [];
567
+ if (!state.messageAddedIds.has(itemId)) {
568
+ events.push({
569
+ type: "response.output_item.added",
570
+ output_index: state.lastOutputIndex,
571
+ item: { type: "message", id: itemId }
572
+ });
573
+ state.messageAddedIds.add(itemId);
574
+ }
575
+ events.push({ ...event, item_id: itemId, delta: typeof event.delta === "string" ? event.delta : "" });
576
+ return events;
577
+ }
578
+ if (event.type === "response.function_call_arguments.delta") {
579
+ const entry = resolveFunctionCall(state, {
580
+ itemId: asString(event.item_id),
581
+ outputIndex: typeof event.output_index === "number" ? event.output_index : void 0
582
+ });
583
+ const delta = typeof event.delta === "string" ? event.delta : "";
584
+ entry.args += delta;
585
+ entry.deltaForwarded = true;
586
+ state.lastFunctionCall = entry;
587
+ state.lastOutputIndex = entry.outputIndex;
588
+ return [{ ...event, item_id: entry.itemId, output_index: entry.outputIndex, delta }];
589
+ }
590
+ if (event.type === "response.completed" || event.type === "response.incomplete") {
591
+ const response = isRecord(event.response) ? event.response : {};
592
+ const recovered = recoverFromCompletedOutput(response, state);
593
+ if (state.lastMessageItemId && state.textDeltaForwarded && !state.messageDoneIds.has(state.lastMessageItemId)) {
594
+ recovered.push({
595
+ type: "response.output_item.done",
596
+ output_index: state.lastOutputIndex,
597
+ item: { type: "message", id: state.lastMessageItemId }
598
+ });
599
+ state.messageDoneIds.add(state.lastMessageItemId);
600
+ }
601
+ return [...recovered, event];
602
+ }
603
+ return [event];
604
+ }
292
605
  function toHeaderRecord(headers) {
293
606
  const out = {};
294
607
  if (!headers) return out;
@@ -346,10 +659,14 @@ function createResponsesWebSocketFetch(wsUrl, log) {
346
659
  if (hasResponsesLiteHeader(headers)) {
347
660
  payload = applyResponsesLiteShape(payload);
348
661
  }
662
+ debug(
663
+ `request type=response.create keys=${Object.keys(payload).sort().join(",")} toolCount=${Array.isArray(payload.tools) ? payload.tools.length : 0} store=${String(payload.store)} parallelToolCalls=${String(payload.parallel_tool_calls)} reasoningKeys=${recordKeys(payload.reasoning)}`
664
+ );
349
665
  const outgoing = JSON.stringify({ type: "response.create", ...payload });
350
666
  const encoder = new TextEncoder();
351
667
  let socket;
352
668
  let frameCount = 0;
669
+ const normalizeState = createResponsesLiteNormalizeState();
353
670
  const stream = new ReadableStream({
354
671
  start(controller) {
355
672
  let closed = false;
@@ -367,13 +684,12 @@ function createResponsesWebSocketFetch(wsUrl, log) {
367
684
  };
368
685
  const fail = (message) => {
369
686
  if (closed) return;
370
- debug(`fail: ${message}`);
687
+ debug(`fail messageChars=${message.length}`);
371
688
  try {
372
- controller.enqueue(encoder.encode(
373
- `data: ${JSON.stringify({ type: "error", error: { message } })}
689
+ const [errorEvent] = normalizeResponsesLiteEvent({ type: "error", error: { message } }, normalizeState);
690
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(errorEvent)}
374
691
 
375
- `
376
- ));
692
+ `));
377
693
  } catch {
378
694
  }
379
695
  close();
@@ -389,28 +705,31 @@ function createResponsesWebSocketFetch(wsUrl, log) {
389
705
  socket.on("message", (data) => {
390
706
  const text = Array.isArray(data) ? Buffer.concat(data).toString("utf8") : data.toString("utf8");
391
707
  frameCount += 1;
392
- if (frameCount <= 3) debug(`frame#${frameCount}: ${text.slice(0, 200)}`);
393
708
  let event;
394
709
  try {
395
710
  event = JSON.parse(text);
396
711
  } catch {
712
+ debug(`frame#${frameCount} non-json chars=${text.length}`);
397
713
  controller.enqueue(encoder.encode(`data: ${text.replace(/\r?\n/g, " ")}
398
714
 
399
715
  `));
400
716
  return;
401
717
  }
402
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}
718
+ if (frameCount <= 8) debug(`frame#${frameCount} ${summarizeResponsesLiteEvent(event)}`);
719
+ for (const next of normalizeResponsesLiteEvent(event, normalizeState)) {
720
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(next)}
403
721
 
404
722
  `));
405
- const type = event.type;
406
- if (typeof type === "string" && TERMINAL_EVENT_TYPES.has(type)) {
723
+ }
724
+ const type = isRecord(event) && typeof event.type === "string" ? event.type : void 0;
725
+ if (type && TERMINAL_EVENT_TYPES.has(type)) {
407
726
  debug(`terminal event: ${type} (after ${frameCount} frames)`);
408
727
  close();
409
728
  }
410
729
  });
411
730
  socket.on("error", (err) => fail(err.message));
412
731
  socket.on("close", (code, reason) => {
413
- debug(`close code=${code} frames=${frameCount}${reason?.length ? ` reason=${reason.toString("utf8").slice(0, 200)}` : ""}`);
732
+ debug(`close code=${code} frames=${frameCount}${reason?.length ? ` reasonChars=${reason.length}` : ""}`);
414
733
  if (closed) return;
415
734
  if (code === 1e3 || code === 1005) {
416
735
  close();
@@ -694,10 +1013,11 @@ async function createLanguageModel(spec) {
694
1013
  return model;
695
1014
  }
696
1015
  var ANTHROPIC_EFFORT_LEVELS = ["low", "medium", "high"];
697
- var OPENAI_EFFORT_LEVELS = ["low", "medium", "high", "xhigh"];
1016
+ var OPENAI_EFFORT_LEVELS = ["low", "medium", "high"];
698
1017
  var GEMINI_EFFORT_LEVELS = ["low", "medium", "high"];
699
1018
  var MISTRAL_EFFORT_LEVELS = ["high", "off"];
700
- var XAI_EFFORT_LEVELS = ["none", "low", "medium", "high"];
1019
+ var XAI_CHAT_EFFORT_LEVELS = ["low", "high"];
1020
+ var XAI_RESPONSES_EFFORT_LEVELS = ["low", "medium", "high"];
701
1021
  var OPENROUTER_EFFORT_LEVELS = ["none", "minimal", "low", "medium", "high", "xhigh"];
702
1022
  var DEEPSEEK_EFFORT_LEVELS = ["high", "max", "off"];
703
1023
  var GLM_52_EFFORT_LEVELS = ["high", "xhigh"];
@@ -709,6 +1029,15 @@ var EMPTY_REASONING = {
709
1029
  source: "none",
710
1030
  confidence: "inferred"
711
1031
  };
1032
+ var GEMINI_25_BUDGETS = {
1033
+ low: 1024,
1034
+ medium: 4096,
1035
+ high: 8192,
1036
+ xhigh: 16384,
1037
+ max: 16384,
1038
+ minimal: 512,
1039
+ none: 0
1040
+ };
712
1041
  function isClaudeReasoningModel(modelId) {
713
1042
  const lower = modelId.toLowerCase();
714
1043
  if (!lower.startsWith("claude-")) return false;
@@ -723,6 +1052,10 @@ function isGeminiReasoningModel(modelId) {
723
1052
  const lower = modelId.toLowerCase();
724
1053
  return lower.startsWith("gemini-2.5-") || lower.startsWith("gemini-3") || lower.startsWith("gemini-3.");
725
1054
  }
1055
+ function isGemini3Model(modelId) {
1056
+ const lower = modelId.toLowerCase();
1057
+ return lower.startsWith("gemini-3") || lower.startsWith("gemini-3.");
1058
+ }
726
1059
  function isMistralReasoningModel(modelId) {
727
1060
  const lower = modelId.toLowerCase();
728
1061
  return lower.startsWith("mistral-") || lower.startsWith("magistral-") || lower.startsWith("ministral-") || lower.includes("reasoning");
@@ -755,6 +1088,9 @@ function isGlm52ReasoningModel(modelId) {
755
1088
  const lower = modelId.toLowerCase();
756
1089
  return lower === "glm-5.2" || lower === "z-ai/glm-5.2" || lower === "zai/glm-5.2" || lower === "zai-org/glm-5.2" || lower === "zai-org/glm5.2" || lower === "glm5.2";
757
1090
  }
1091
+ function toCamelCase(str) {
1092
+ return str.replace(/[-_]([a-z])/g, (_, g) => g.toUpperCase());
1093
+ }
758
1094
  function hasSupportedParameter(metadata, param) {
759
1095
  return (metadata?.supportedParameters ?? []).some((p) => p === param);
760
1096
  }
@@ -790,7 +1126,179 @@ function openRouterReasoningCapabilities(metadata) {
790
1126
  }
791
1127
  return EMPTY_REASONING;
792
1128
  }
1129
+ function mapCodexEffortToDeepSeek(effort) {
1130
+ switch (effort) {
1131
+ case "off":
1132
+ case "none":
1133
+ return "off";
1134
+ case "low":
1135
+ case "medium":
1136
+ case "high":
1137
+ return "high";
1138
+ case "xhigh":
1139
+ case "max":
1140
+ return "max";
1141
+ default:
1142
+ if (effort === "high" || effort === "max") return effort;
1143
+ return void 0;
1144
+ }
1145
+ }
1146
+ function deepSeekEffortProviderOptions(effort) {
1147
+ const mapped = mapCodexEffortToDeepSeek(effort);
1148
+ if (!mapped) return void 0;
1149
+ const thinking = { type: mapped === "off" ? "disabled" : "enabled" };
1150
+ const spread = { thinking };
1151
+ if (mapped === "off") {
1152
+ return {
1153
+ deepseek: spread,
1154
+ openaiCompatible: spread
1155
+ };
1156
+ }
1157
+ return {
1158
+ openaiCompatible: { reasoningEffort: mapped, ...spread },
1159
+ deepseek: spread
1160
+ };
1161
+ }
1162
+ function mapCodexEffortToAnthropic(effort) {
1163
+ switch (effort) {
1164
+ case "none":
1165
+ case "minimal":
1166
+ case "low":
1167
+ return "low";
1168
+ case "medium":
1169
+ return "medium";
1170
+ case "high":
1171
+ case "xhigh":
1172
+ case "max":
1173
+ return effort === "xhigh" ? "high" : effort === "max" ? "max" : "high";
1174
+ default:
1175
+ if (ANTHROPIC_EFFORT_LEVELS.includes(effort)) {
1176
+ return effort;
1177
+ }
1178
+ return void 0;
1179
+ }
1180
+ }
1181
+ var OPENAI_MODEL_REASONING = {
1182
+ "gpt-5-pro": { levels: ["high"], defaultLevel: "high" },
1183
+ "gpt-5.1": { levels: ["none", "low", "medium", "high"], defaultLevel: "none" },
1184
+ "gpt-5.1-codex-max": { levels: ["low", "medium", "high", "xhigh"], defaultLevel: "medium" },
1185
+ "gpt-5.2": { levels: ["none", "low", "medium", "high", "xhigh"], defaultLevel: "none" },
1186
+ "gpt-5.2-codex": { levels: ["low", "medium", "high", "xhigh"], defaultLevel: "medium" },
1187
+ "gpt-5.2-pro": { levels: ["medium", "high", "xhigh"], defaultLevel: "medium" },
1188
+ "gpt-5.3-codex": { levels: ["low", "medium", "high", "xhigh"], defaultLevel: "medium" },
1189
+ "gpt-5.4": { levels: ["none", "low", "medium", "high", "xhigh"], defaultLevel: "none" },
1190
+ "gpt-5.4-mini": { levels: ["none", "low", "medium", "high", "xhigh"], defaultLevel: "none" },
1191
+ "gpt-5.4-nano": { levels: ["none", "low", "medium", "high", "xhigh"], defaultLevel: "none" },
1192
+ "gpt-5.4-pro": { levels: ["medium", "high", "xhigh"], defaultLevel: "medium" },
1193
+ "gpt-5.5": { levels: ["none", "low", "medium", "high", "xhigh"], defaultLevel: "medium" },
1194
+ "gpt-5.5-pro": { levels: ["medium", "high", "xhigh"], defaultLevel: "high" },
1195
+ "gpt-5.6": { levels: ["none", "low", "medium", "high", "xhigh", "max"], defaultLevel: "medium" },
1196
+ "gpt-5.6-luna": { levels: ["none", "low", "medium", "high", "xhigh", "max"], defaultLevel: "medium" },
1197
+ "gpt-5.6-sol": { levels: ["none", "low", "medium", "high", "xhigh", "max"], defaultLevel: "medium" },
1198
+ "gpt-5.6-terra": { levels: ["none", "low", "medium", "high", "xhigh", "max"], defaultLevel: "medium" }
1199
+ };
1200
+ var OPENAI_NON_REASONING_MODELS = /* @__PURE__ */ new Set([
1201
+ "chat-latest",
1202
+ "gpt-5-chat-latest",
1203
+ "gpt-5.1-chat-latest",
1204
+ "gpt-5.2-chat-latest",
1205
+ "gpt-5.3-chat-latest"
1206
+ ]);
1207
+ var OPENAI_DATED_SNAPSHOT_SUFFIX = /-\d{4}-\d{2}-\d{2}$/;
1208
+ function canonicalOpenAiModelId(modelId, metadata) {
1209
+ return (metadata?.upstreamModelId ?? modelId ?? "").toLowerCase();
1210
+ }
1211
+ function openAiReasoningProfile(modelId, metadata) {
1212
+ const id = canonicalOpenAiModelId(modelId, metadata);
1213
+ if (!id) return void 0;
1214
+ return OPENAI_MODEL_REASONING[id] ?? OPENAI_MODEL_REASONING[id.replace(OPENAI_DATED_SNAPSHOT_SUFFIX, "")];
1215
+ }
1216
+ function openAiModelReasons(modelId, metadata) {
1217
+ const id = canonicalOpenAiModelId(modelId, metadata);
1218
+ if (OPENAI_NON_REASONING_MODELS.has(id.replace(OPENAI_DATED_SNAPSHOT_SUFFIX, ""))) return false;
1219
+ return !!openAiReasoningProfile(modelId, metadata) || modelPrefersResponsesApi(id) || !!metadata?.reasoning;
1220
+ }
1221
+ function mapCodexEffortToOpenAI(effort, allowed) {
1222
+ return allowed.includes(effort) ? effort : void 0;
1223
+ }
1224
+ function mapCodexEffortToOpenAICompatible(effort) {
1225
+ if (effort === "xhigh") return "high";
1226
+ const allowed = ["low", "medium", "high"];
1227
+ return allowed.includes(effort) ? effort : void 0;
1228
+ }
1229
+ function mapCodexEffortToGlm52(effort) {
1230
+ switch (effort) {
1231
+ case "high":
1232
+ return "high";
1233
+ case "xhigh":
1234
+ case "max":
1235
+ return "max";
1236
+ default:
1237
+ return void 0;
1238
+ }
1239
+ }
1240
+ function mapCodexEffortToXai(effort, supportsMedium) {
1241
+ switch (effort) {
1242
+ case "low":
1243
+ return "low";
1244
+ case "medium":
1245
+ return supportsMedium ? "medium" : void 0;
1246
+ case "high":
1247
+ case "xhigh":
1248
+ case "max":
1249
+ return "high";
1250
+ default:
1251
+ return void 0;
1252
+ }
1253
+ }
1254
+ function mapCodexEffortToGeminiLevel(effort) {
1255
+ switch (effort) {
1256
+ case "none":
1257
+ case "minimal":
1258
+ case "low":
1259
+ return "low";
1260
+ case "medium":
1261
+ return "medium";
1262
+ case "high":
1263
+ case "xhigh":
1264
+ case "max":
1265
+ return "high";
1266
+ default:
1267
+ return GEMINI_EFFORT_LEVELS.includes(effort) ? effort : void 0;
1268
+ }
1269
+ }
1270
+ function mapCodexEffortToGeminiBudget(effort) {
1271
+ const direct = GEMINI_25_BUDGETS[effort];
1272
+ if (direct !== void 0) return direct > 0 ? direct : void 0;
1273
+ const level = mapCodexEffortToGeminiLevel(effort);
1274
+ if (!level) return void 0;
1275
+ return GEMINI_25_BUDGETS[level];
1276
+ }
1277
+ function withMappableLevels(caps, npm, modelId, metadata) {
1278
+ if (caps.mode !== "controllable") return caps;
1279
+ const seen = /* @__PURE__ */ new Set();
1280
+ const levels = caps.levels.filter((level) => {
1281
+ const mapped = effortProviderOptions(npm, level, modelId, metadata);
1282
+ if (mapped === void 0) return false;
1283
+ const wire = JSON.stringify(mapped);
1284
+ if (seen.has(wire)) return false;
1285
+ seen.add(wire);
1286
+ return true;
1287
+ });
1288
+ if (levels.length === caps.levels.length) return caps;
1289
+ if (levels.length === 0) {
1290
+ return { ...caps, levels: [], defaultLevel: "", mode: "internal-only" };
1291
+ }
1292
+ return {
1293
+ ...caps,
1294
+ levels,
1295
+ defaultLevel: levels.includes(caps.defaultLevel) ? caps.defaultLevel : levels[levels.length - 1]
1296
+ };
1297
+ }
793
1298
  function getReasoningCapabilities(npm, modelId, metadata) {
1299
+ return withMappableLevels(resolveRawReasoningCapabilities(npm, modelId, metadata), npm, modelId, metadata);
1300
+ }
1301
+ function resolveRawReasoningCapabilities(npm, modelId, metadata) {
794
1302
  const id = modelId.toLowerCase();
795
1303
  if (isOpenRouterRoute(npm, metadata)) {
796
1304
  return openRouterReasoningCapabilities(metadata);
@@ -811,15 +1319,18 @@ function getReasoningCapabilities(npm, modelId, metadata) {
811
1319
  return EMPTY_REASONING;
812
1320
  }
813
1321
  if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
814
- const prefersResponses = modelPrefersResponsesApi(modelId);
815
- if (prefersResponses || metadata?.reasoning) {
1322
+ const canonicalId = canonicalOpenAiModelId(modelId, metadata);
1323
+ const profile = openAiReasoningProfile(modelId, metadata);
1324
+ const prefersResponses = modelPrefersResponsesApi(canonicalId);
1325
+ if (openAiModelReasons(modelId, metadata) && shouldUseOpenAiResponsesEndpoint(canonicalId)) {
1326
+ const levels = profile?.levels ?? [...OPENAI_EFFORT_LEVELS];
816
1327
  return {
817
- levels: [...OPENAI_EFFORT_LEVELS],
818
- defaultLevel: "medium",
1328
+ levels: [...levels],
1329
+ defaultLevel: profile?.defaultLevel ?? (levels.includes("medium") ? "medium" : levels[levels.length - 1]),
819
1330
  supportsSummaries: true,
1331
+ source: profile || prefersResponses ? "provider-rule" : "model-metadata",
1332
+ confidence: profile || prefersResponses ? "documented" : "inferred",
820
1333
  mode: "controllable",
821
- source: prefersResponses ? "provider-rule" : "model-metadata",
822
- confidence: prefersResponses ? "documented" : "inferred",
823
1334
  wireFormat: { kind: "openai-reasoning-effort" }
824
1335
  };
825
1336
  }
@@ -855,7 +1366,7 @@ function getReasoningCapabilities(npm, modelId, metadata) {
855
1366
  }
856
1367
  if (npm === "@ai-sdk/xai") {
857
1368
  if (isXaiReasoningEffortModel(modelId)) {
858
- const levels = modelPrefersResponsesApi(modelId) ? ["low", "medium", "high", "xhigh"] : [...XAI_EFFORT_LEVELS];
1369
+ const levels = modelPrefersResponsesApi(modelId) ? [...XAI_RESPONSES_EFFORT_LEVELS] : [...XAI_CHAT_EFFORT_LEVELS];
859
1370
  return {
860
1371
  levels,
861
1372
  defaultLevel: xaiDefaultReasoningEffort(modelId),
@@ -936,6 +1447,91 @@ function getReasoningCapabilities(npm, modelId, metadata) {
936
1447
  }
937
1448
  return EMPTY_REASONING;
938
1449
  }
1450
+ function effortProviderOptions(npm, effort, modelId, metadata) {
1451
+ if (!effort) return void 0;
1452
+ if (isOpenRouterRoute(npm, metadata)) {
1453
+ const caps = openRouterReasoningCapabilities(metadata);
1454
+ if (caps.mode !== "controllable") return void 0;
1455
+ const allowed = new Set(OPENROUTER_EFFORT_LEVELS);
1456
+ const mapped = allowed.has(effort) ? effort : effort === "max" ? "xhigh" : void 0;
1457
+ return mapped ? { openrouter: { reasoning: { effort: mapped, exclude: false } } } : void 0;
1458
+ }
1459
+ if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
1460
+ if (!modelId || !shouldUseOpenAiResponsesEndpoint(canonicalOpenAiModelId(modelId, metadata))) return void 0;
1461
+ if (!openAiModelReasons(modelId, metadata)) return void 0;
1462
+ const allowed = openAiReasoningProfile(modelId, metadata)?.levels ?? OPENAI_EFFORT_LEVELS;
1463
+ const reasoningEffort = mapCodexEffortToOpenAI(effort, allowed);
1464
+ return reasoningEffort ? { openai: { reasoningEffort } } : void 0;
1465
+ }
1466
+ if (npm === "@ai-sdk/xai") {
1467
+ if (!modelId || !isXaiReasoningEffortModel(modelId)) return void 0;
1468
+ const reasoningEffort = mapCodexEffortToXai(effort, modelPrefersResponsesApi(modelId));
1469
+ return reasoningEffort ? { xai: { reasoningEffort } } : void 0;
1470
+ }
1471
+ if (npm === "@ai-sdk/anthropic" || npm === VERTEX_ANTHROPIC_NPM) {
1472
+ if (!modelId || !isClaudeReasoningModel(modelId)) return void 0;
1473
+ const mapped = mapCodexEffortToAnthropic(effort);
1474
+ return mapped ? { anthropic: { thinking: { type: "adaptive", effort: mapped } } } : void 0;
1475
+ }
1476
+ if (npm === "@ai-sdk/google") {
1477
+ const id = modelId ?? "";
1478
+ if (isGemini3Model(id)) {
1479
+ const thinkingLevel = mapCodexEffortToGeminiLevel(effort);
1480
+ return thinkingLevel ? { google: { thinkingConfig: { thinkingLevel, includeThoughts: true } } } : void 0;
1481
+ }
1482
+ const thinkingBudget = mapCodexEffortToGeminiBudget(effort);
1483
+ return thinkingBudget ? { google: { thinkingConfig: { thinkingBudget, includeThoughts: true } } } : void 0;
1484
+ }
1485
+ if (npm === "@ai-sdk/mistral") {
1486
+ if (!modelId || !isMistralReasoningModel(modelId)) return void 0;
1487
+ const reasoningEffort = effort === "off" || effort === "none" ? "none" : "high";
1488
+ return { mistral: { reasoningEffort } };
1489
+ }
1490
+ if (npm === "@ai-sdk/openai-compatible" || npm === "@ai-sdk/openai") {
1491
+ if (!modelId) return void 0;
1492
+ if (isDeepSeekReasoningModel(modelId)) {
1493
+ return deepSeekEffortProviderOptions(effort);
1494
+ }
1495
+ if (isKimiReasoningModel(modelId)) {
1496
+ const reasoningEffort = mapCodexEffortToOpenAICompatible(effort);
1497
+ if (reasoningEffort) {
1498
+ const key = metadata?.providerId ? toCamelCase(metadata.providerId) : "openaiCompatible";
1499
+ return { [key]: { reasoningEffort } };
1500
+ }
1501
+ return void 0;
1502
+ }
1503
+ if (isGlm52ReasoningModel(modelId)) {
1504
+ const reasoningEffort = mapCodexEffortToGlm52(effort);
1505
+ if (reasoningEffort) {
1506
+ const key = metadata?.providerId ? toCamelCase(metadata.providerId) : "openaiCompatible";
1507
+ return { [key]: { reasoningEffort } };
1508
+ }
1509
+ return void 0;
1510
+ }
1511
+ if (hasSupportedParameter(metadata, "reasoning_effort")) {
1512
+ const reasoningEffort = mapCodexEffortToOpenAICompatible(effort);
1513
+ return reasoningEffort ? { openai: { reasoningEffort }, openaiCompatible: { reasoningEffort } } : void 0;
1514
+ }
1515
+ if (hasSupportedParameter(metadata, "reasoning")) {
1516
+ const allowed = new Set(OPENROUTER_EFFORT_LEVELS);
1517
+ const mapped = allowed.has(effort) ? effort : effort === "max" ? "xhigh" : void 0;
1518
+ return mapped ? { openrouter: { reasoning: { effort: mapped, exclude: false } } } : void 0;
1519
+ }
1520
+ return void 0;
1521
+ }
1522
+ return void 0;
1523
+ }
1524
+ function deepMergeProviderOptions(a, b) {
1525
+ if (!a && !b) return void 0;
1526
+ if (!a) return b;
1527
+ if (!b) return a;
1528
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
1529
+ const out = {};
1530
+ for (const key of keys) {
1531
+ out[key] = { ...a[key] ?? {}, ...b[key] ?? {} };
1532
+ }
1533
+ return out;
1534
+ }
939
1535
 
940
1536
  // src/registry/io.ts
941
1537
  import {
@@ -1151,6 +1747,7 @@ var DEFAULT_RETRYABLE = {
1151
1747
  CREDENTIAL_UNAVAILABLE: false,
1152
1748
  OAUTH_REFRESH_FAILED: true,
1153
1749
  UNSUPPORTED_MODEL: false,
1750
+ UNSUPPORTED_REASONING_LEVEL: false,
1154
1751
  UNSUPPORTED_REGISTRY_VERSION: false,
1155
1752
  PROVIDER_LOAD_FAILED: true
1156
1753
  };
@@ -1182,6 +1779,80 @@ function isRelayCoreError(err) {
1182
1779
  return err instanceof RelayCoreError;
1183
1780
  }
1184
1781
 
1782
+ // src/core/reasoning.ts
1783
+ var RELAY_REASONING_LEVELS = [
1784
+ "off",
1785
+ "none",
1786
+ "minimal",
1787
+ "low",
1788
+ "medium",
1789
+ "high",
1790
+ "xhigh",
1791
+ "max"
1792
+ ];
1793
+ function isRelayReasoningLevel(value) {
1794
+ return typeof value === "string" && RELAY_REASONING_LEVELS.includes(value);
1795
+ }
1796
+ function reasoningNpmForRoute(provider, model) {
1797
+ if (model.modelFormat === "cloud-code") return "@ai-sdk/google";
1798
+ return model.npm ?? provider.api.npm ?? "";
1799
+ }
1800
+ function resolveReasoningProviderOptions(level, provider, model, routeId) {
1801
+ if (!isRelayReasoningLevel(level)) {
1802
+ throw new RelayCoreError(
1803
+ "UNSUPPORTED_REASONING_LEVEL",
1804
+ `Unknown reasoning level "${String(level)}" \u2014 expected one of: ${RELAY_REASONING_LEVELS.join(", ")}.`,
1805
+ { providerId: provider.id, routeId }
1806
+ );
1807
+ }
1808
+ const npm = reasoningNpmForRoute(provider, model);
1809
+ const upstreamModelId = model.upstreamModelId ?? model.id;
1810
+ const metadata = {
1811
+ providerId: provider.id,
1812
+ upstreamModelId,
1813
+ ...model.apiUrl ?? provider.api.url ? { apiBaseUrl: model.apiUrl ?? provider.api.url } : {},
1814
+ ...model.supportedParameters ? { supportedParameters: model.supportedParameters } : {},
1815
+ ...model.reasoning !== void 0 ? { reasoning: model.reasoning } : {},
1816
+ ...model.interleavedReasoningField ? { interleavedReasoningField: model.interleavedReasoningField } : {}
1817
+ };
1818
+ const caps = getReasoningCapabilities(npm, upstreamModelId, metadata);
1819
+ if (caps.mode !== "controllable" || !caps.levels.includes(level)) {
1820
+ const available = caps.levels.length > 0 ? caps.levels.join(", ") : "none";
1821
+ throw new RelayCoreError(
1822
+ "UNSUPPORTED_REASONING_LEVEL",
1823
+ `Model "${model.id}" on provider "${provider.name}" does not support reasoning level "${level}" \u2014 available levels: ${available}. See capabilities.reasoningLevels from listRelayModels().`,
1824
+ { providerId: provider.id, routeId }
1825
+ );
1826
+ }
1827
+ const resolved = effortProviderOptions(npm, level, upstreamModelId, metadata);
1828
+ if (!resolved) {
1829
+ throw new RelayCoreError(
1830
+ "UNSUPPORTED_REASONING_LEVEL",
1831
+ `Model "${model.id}" on provider "${provider.name}" advertises reasoning level "${level}" but Relay has no request mapping for it \u2014 this is a relay-ai bug, please report it.`,
1832
+ { providerId: provider.id, routeId }
1833
+ );
1834
+ }
1835
+ return resolved;
1836
+ }
1837
+ async function withReasoningProviderOptions(model, providerOptions) {
1838
+ const { wrapLanguageModel: wrapLanguageModel2 } = await import("ai");
1839
+ return wrapLanguageModel2({
1840
+ // `LanguageModel` also admits a bare model-id string and the legacy v2
1841
+ // interface; everything Core builds is a concrete current-spec model.
1842
+ model,
1843
+ middleware: {
1844
+ specificationVersion: "v3",
1845
+ transformParams: async ({ params }) => ({
1846
+ ...params,
1847
+ providerOptions: deepMergeProviderOptions(
1848
+ providerOptions,
1849
+ params.providerOptions
1850
+ )
1851
+ })
1852
+ }
1853
+ });
1854
+ }
1855
+
1185
1856
  // src/core/route-id.ts
1186
1857
  var SEPARATOR = "::";
1187
1858
  function toRelayRouteId(providerId, modelId) {
@@ -1219,7 +1890,7 @@ function loadCoreRegistry(path) {
1219
1890
  }
1220
1891
  function mapReasoning(provider, model) {
1221
1892
  const base = { tools: "unknown", vision: "unknown" };
1222
- const npm = model.npm ?? provider.api.npm ?? "";
1893
+ const npm = reasoningNpmForRoute(provider, model);
1223
1894
  const upstreamModelId = model.upstreamModelId ?? model.id;
1224
1895
  try {
1225
1896
  const caps = getReasoningCapabilities(npm, upstreamModelId, {
@@ -1235,13 +1906,16 @@ function mapReasoning(provider, model) {
1235
1906
  return { ...base, reasoning: "none" };
1236
1907
  case "internal-only":
1237
1908
  return { ...base, reasoning: "fixed" };
1238
- case "controllable":
1909
+ case "controllable": {
1910
+ const levels = caps.levels.filter(isRelayReasoningLevel);
1911
+ if (levels.length === 0) return { ...base, reasoning: "fixed" };
1239
1912
  return {
1240
1913
  ...base,
1241
1914
  reasoning: "adjustable",
1242
- reasoningLevels: [...caps.levels],
1243
- defaultReasoningLevel: caps.defaultLevel
1915
+ reasoningLevels: levels,
1916
+ ...isRelayReasoningLevel(caps.defaultLevel) ? { defaultReasoningLevel: caps.defaultLevel } : {}
1244
1917
  };
1918
+ }
1245
1919
  default:
1246
1920
  return { ...base, reasoning: "unknown" };
1247
1921
  }
@@ -1513,6 +2187,12 @@ var SCOPES = [
1513
2187
  ].join(" ");
1514
2188
  var ANTIGRAVITY_VERSION = "4.2.0";
1515
2189
  var ANTIGRAVITY_USER_AGENT = `vscode/1.X.X (Antigravity/${ANTIGRAVITY_VERSION})`;
2190
+ var ANTIGRAVITY_BASE_URLS = [
2191
+ "https://daily-cloudcode-pa.googleapis.com",
2192
+ "https://cloudcode-pa.googleapis.com",
2193
+ "https://daily-cloudcode-pa.sandbox.googleapis.com"
2194
+ ];
2195
+ var ANTIGRAVITY_API_VERSION = "v1internal";
1516
2196
  async function refreshAntigravityToken(refreshToken) {
1517
2197
  return postOAuthRefresh(
1518
2198
  TOKEN_URL3,
@@ -2401,7 +3081,244 @@ function providerRefreshToken(providerId, authType, authRef) {
2401
3081
  return () => forceRefreshProviderCredential(providerId, authRef ?? oauthAuthRef(providerId));
2402
3082
  }
2403
3083
 
3084
+ // src/core/antigravity-model.ts
3085
+ import { randomUUID as randomUUID2 } from "crypto";
3086
+ var CLOUD_CODE_BASES = ANTIGRAVITY_BASE_URLS.map((base) => base.replace(/\/+$/, ""));
3087
+ var CLOUD_CODE_BASE = CLOUD_CODE_BASES[0];
3088
+ var STREAM_URLS = CLOUD_CODE_BASES.map((base) => `${base}/${ANTIGRAVITY_API_VERSION}:streamGenerateContent?alt=sse`);
3089
+ var UNARY_URLS = CLOUD_CODE_BASES.map((base) => `${base}/${ANTIGRAVITY_API_VERSION}:generateContent`);
3090
+ var SDK_BASE_URL = `${CLOUD_CODE_BASE}/v1beta`;
3091
+ var ENDPOINT_FAILOVER_STATUSES = /* @__PURE__ */ new Set([404, 408, 429]);
3092
+ function shouldTryNextEndpoint(status) {
3093
+ return ENDPOINT_FAILOVER_STATUSES.has(status) || status >= 500;
3094
+ }
3095
+ function discardResponse(response) {
3096
+ try {
3097
+ void response.body?.cancel();
3098
+ } catch {
3099
+ }
3100
+ }
3101
+ function unwrapCloudCodeSsePayload(payload) {
3102
+ const trimmed = payload.trim();
3103
+ if (trimmed === "" || trimmed === "[DONE]") return payload;
3104
+ try {
3105
+ const parsed = JSON.parse(trimmed);
3106
+ if (isWrappedCloudCodeBody(parsed)) {
3107
+ return JSON.stringify(parsed.response);
3108
+ }
3109
+ } catch {
3110
+ }
3111
+ return payload;
3112
+ }
3113
+ function unwrapCloudCodeJsonBody(text) {
3114
+ try {
3115
+ const parsed = JSON.parse(text);
3116
+ if (isWrappedCloudCodeBody(parsed)) {
3117
+ return JSON.stringify(parsed.response);
3118
+ }
3119
+ } catch {
3120
+ }
3121
+ return text;
3122
+ }
3123
+ function consumeCloudCodeSseBuffer(buffer) {
3124
+ const separator = /\r?\n\r?\n/;
3125
+ let rest = buffer;
3126
+ let emitted = "";
3127
+ while (true) {
3128
+ const match = separator.exec(rest);
3129
+ if (!match || match.index === void 0) break;
3130
+ const rawEvent = rest.slice(0, match.index);
3131
+ const sep = match[0];
3132
+ rest = rest.slice(match.index + sep.length);
3133
+ emitted += transformSseEvent(rawEvent) + sep;
3134
+ }
3135
+ return { emitted, rest };
3136
+ }
3137
+ function createCloudCodeSseUnwrapper() {
3138
+ const decoder = new TextDecoder();
3139
+ const encoder = new TextEncoder();
3140
+ let pending = "";
3141
+ return new TransformStream({
3142
+ transform(chunk, controller) {
3143
+ pending += decoder.decode(chunk, { stream: true });
3144
+ const { emitted, rest } = consumeCloudCodeSseBuffer(pending);
3145
+ pending = rest;
3146
+ if (emitted) controller.enqueue(encoder.encode(emitted));
3147
+ },
3148
+ flush(controller) {
3149
+ pending += decoder.decode();
3150
+ if (!pending) return;
3151
+ const { emitted, rest } = consumeCloudCodeSseBuffer(pending);
3152
+ const tail = emitted + (rest ? transformSseEvent(rest) : "");
3153
+ if (tail) controller.enqueue(encoder.encode(tail));
3154
+ }
3155
+ });
3156
+ }
3157
+ function createCloudCodeFetch(options, fetchImpl) {
3158
+ let accessToken = options.accessToken;
3159
+ const debug = (msg) => {
3160
+ try {
3161
+ options.onDebug?.(`cloud-code: ${msg}`);
3162
+ } catch {
3163
+ }
3164
+ };
3165
+ return async (input, init) => {
3166
+ const url = requestUrl(input);
3167
+ const streaming = url.includes("streamGenerateContent");
3168
+ const signal = init?.signal ?? (input instanceof Request ? input.signal : void 0);
3169
+ const geminiBody = await readJsonBody(input, init);
3170
+ const envelope = {
3171
+ project: options.projectId,
3172
+ requestId: randomUUID2(),
3173
+ model: options.modelId,
3174
+ userAgent: ANTIGRAVITY_USER_AGENT,
3175
+ requestType: "agent",
3176
+ enabledCreditTypes: ["GOOGLE_ONE_AI"],
3177
+ request: geminiBody
3178
+ };
3179
+ const body = JSON.stringify(envelope);
3180
+ const bodyByteLength = Buffer.byteLength(body, "utf8");
3181
+ const upstreamUrls = streaming ? STREAM_URLS : UNARY_URLS;
3182
+ const doFetch = fetchImpl ?? ((input2, init2) => globalThis.fetch(input2, init2));
3183
+ const send = async (url2, token) => {
3184
+ try {
3185
+ return await doFetch(url2, {
3186
+ method: "POST",
3187
+ headers: {
3188
+ "Content-Type": "application/json",
3189
+ Authorization: `Bearer ${token}`,
3190
+ "User-Agent": ANTIGRAVITY_USER_AGENT
3191
+ },
3192
+ body,
3193
+ signal
3194
+ });
3195
+ } catch (err) {
3196
+ if (isAbortError(err, signal)) throw abortError(signal, err);
3197
+ throw err;
3198
+ }
3199
+ };
3200
+ const sendWithFailover = async (token, startIndex = 0) => {
3201
+ let lastError;
3202
+ for (let i = startIndex; i < upstreamUrls.length; i += 1) {
3203
+ const url2 = upstreamUrls[i];
3204
+ const isLast = i === upstreamUrls.length - 1;
3205
+ const where = `endpoint=${i + 1}/${upstreamUrls.length} host=${endpointHost(url2)}`;
3206
+ let response2;
3207
+ try {
3208
+ debug(`request ${where} kind=${streaming ? "stream" : "unary"} payloadBytes=${bodyByteLength}`);
3209
+ response2 = await send(url2, token);
3210
+ } catch (err) {
3211
+ if (isAbortError(err, signal)) throw err;
3212
+ lastError = err;
3213
+ debug(`network failure ${where} errorName=${errorName(err)}`);
3214
+ }
3215
+ if (response2) {
3216
+ if (isLast || !shouldTryNextEndpoint(response2.status)) {
3217
+ debug(`response ${where} status=${response2.status}`);
3218
+ return { response: response2, url: url2, index: i };
3219
+ }
3220
+ discardResponse(response2);
3221
+ lastError = new Error(`Cloud Code Assist endpoint returned ${response2.status}`);
3222
+ debug(`retryable status=${response2.status} ${where} \u2014 trying next endpoint`);
3223
+ }
3224
+ if (signal?.aborted) throw abortError(signal);
3225
+ }
3226
+ throw lastError ?? new Error("All Cloud Code Assist endpoints failed");
3227
+ };
3228
+ const tokenUsed = accessToken;
3229
+ let { response, index: servedByIndex } = await sendWithFailover(tokenUsed);
3230
+ if (response.status === 401 && options.refreshToken && !signal?.aborted) {
3231
+ debug("status=401 \u2014 refreshing credential");
3232
+ const refreshed = await options.refreshToken().catch(() => null);
3233
+ if (refreshed && refreshed !== tokenUsed && !signal?.aborted) {
3234
+ accessToken = refreshed;
3235
+ discardResponse(response);
3236
+ ({ response } = await sendWithFailover(refreshed, servedByIndex));
3237
+ debug(`retry after refresh status=${response.status}`);
3238
+ } else {
3239
+ debug(`refresh did not yield a new credential (refreshed=${refreshed ? "same" : "none"})`);
3240
+ }
3241
+ }
3242
+ return adaptUpstreamResponse(response, streaming);
3243
+ };
3244
+ }
3245
+ async function createAntigravityCloudCodeModel(options) {
3246
+ const { createGoogleGenerativeAI } = await import("@ai-sdk/google");
3247
+ const google = createGoogleGenerativeAI({
3248
+ apiKey: "relay-cloud-code",
3249
+ baseURL: SDK_BASE_URL,
3250
+ fetch: createCloudCodeFetch(options)
3251
+ });
3252
+ return google(options.modelId);
3253
+ }
3254
+ function endpointHost(url) {
3255
+ try {
3256
+ return new URL(url).host;
3257
+ } catch {
3258
+ return "unknown";
3259
+ }
3260
+ }
3261
+ function errorName(err) {
3262
+ if (err instanceof Error) return err.name || "Error";
3263
+ return typeof err;
3264
+ }
3265
+ function isWrappedCloudCodeBody(parsed) {
3266
+ return !!parsed && typeof parsed === "object" && !Array.isArray(parsed) && "response" in parsed && parsed.response !== null && typeof parsed.response === "object";
3267
+ }
3268
+ function transformSseEvent(event) {
3269
+ return event.replace(/^(data:[ \t]*)(.*)$/gm, (_all, prefix, payload) => `${prefix}${unwrapCloudCodeSsePayload(payload)}`);
3270
+ }
3271
+ function requestUrl(input) {
3272
+ if (typeof input === "string") return input;
3273
+ if (input instanceof URL) return input.href;
3274
+ return input.url;
3275
+ }
3276
+ async function readJsonBody(input, init) {
3277
+ const body = init?.body;
3278
+ if (typeof body === "string") return JSON.parse(body);
3279
+ if (body instanceof Uint8Array) return JSON.parse(new TextDecoder().decode(body));
3280
+ if (body instanceof ArrayBuffer) return JSON.parse(new TextDecoder().decode(body));
3281
+ const request = input instanceof Request ? input.clone() : new Request(input, init);
3282
+ return request.json();
3283
+ }
3284
+ async function adaptUpstreamResponse(upstream, streaming) {
3285
+ const fallbackType = streaming ? "text/event-stream" : "application/json";
3286
+ const contentType = upstream.headers.get("content-type") ?? fallbackType;
3287
+ const headers = new Headers({ "Content-Type": contentType });
3288
+ if (!upstream.ok) {
3289
+ const errBody = await upstream.text();
3290
+ return new Response(errBody, {
3291
+ status: upstream.status,
3292
+ statusText: upstream.statusText,
3293
+ headers
3294
+ });
3295
+ }
3296
+ if (streaming) {
3297
+ const body = upstream.body ? upstream.body.pipeThrough(createCloudCodeSseUnwrapper()) : null;
3298
+ return new Response(body, {
3299
+ status: upstream.status,
3300
+ statusText: upstream.statusText,
3301
+ headers
3302
+ });
3303
+ }
3304
+ const text = await upstream.text();
3305
+ headers.set("Content-Type", "application/json");
3306
+ return new Response(unwrapCloudCodeJsonBody(text), { status: 200, headers });
3307
+ }
3308
+ function isAbortError(err, signal) {
3309
+ if (signal?.aborted) return true;
3310
+ return !!err && typeof err === "object" && err.name === "AbortError";
3311
+ }
3312
+ function abortError(signal, cause) {
3313
+ if (signal?.reason instanceof Error) return signal.reason;
3314
+ if (cause instanceof Error) return cause;
3315
+ return new DOMException("This operation was aborted", "AbortError");
3316
+ }
3317
+
2404
3318
  // src/core/model.ts
3319
+ function isAntigravityCloudCodeRoute(provider, model) {
3320
+ return provider.id === "antigravity" && provider.authType === "oauth" && model.modelFormat === "cloud-code";
3321
+ }
2405
3322
  function findRoute(registry, providerId, modelId, routeId) {
2406
3323
  const provider = registry.providers.find((p) => p.id === providerId);
2407
3324
  if (!provider) {
@@ -2442,10 +3359,40 @@ async function resolveCredential(provider, routeId) {
2442
3359
  );
2443
3360
  }
2444
3361
  }
2445
- async function createRelayModel(routeId) {
3362
+ async function createRelayModel(routeId, options) {
2446
3363
  const { providerId, modelId } = parseRelayRouteId(routeId);
2447
3364
  const registry = loadCoreRegistry();
2448
3365
  const { provider, model } = findRoute(registry, providerId, modelId, routeId);
3366
+ const reasoningOptions = options?.reasoning === void 0 ? void 0 : resolveReasoningProviderOptions(options.reasoning, provider, model, routeId);
3367
+ const finish = (built) => reasoningOptions ? withReasoningProviderOptions(built, reasoningOptions) : Promise.resolve(built);
3368
+ if (isAntigravityCloudCodeRoute(provider, model)) {
3369
+ const apiKey2 = await resolveCredential(provider, routeId);
3370
+ const providerData2 = await resolveProviderOAuthProviderData(provider.authRef);
3371
+ const projectId = typeof providerData2?.projectId === "string" ? providerData2.projectId.trim() : "";
3372
+ if (!projectId) {
3373
+ throw new RelayCoreError(
3374
+ "CREDENTIAL_UNAVAILABLE",
3375
+ `Provider "${provider.name}" is missing project metadata \u2014 re-authenticate in relay-ai ui.`,
3376
+ { providerId: provider.id, routeId }
3377
+ );
3378
+ }
3379
+ try {
3380
+ return await finish(await createAntigravityCloudCodeModel({
3381
+ modelId: model.upstreamModelId ?? model.id,
3382
+ accessToken: apiKey2,
3383
+ projectId,
3384
+ refreshToken: providerRefreshToken(provider.id, provider.authType, provider.authRef),
3385
+ ...options?.onDebug ? { onDebug: options.onDebug } : {}
3386
+ }));
3387
+ } catch (err) {
3388
+ if (isRelayCoreError(err)) throw err;
3389
+ throw new RelayCoreError(
3390
+ "PROVIDER_LOAD_FAILED",
3391
+ `Failed to construct model "${modelId}" for provider "${provider.name}".`,
3392
+ { providerId, routeId, cause: err }
3393
+ );
3394
+ }
3395
+ }
2449
3396
  const npm = model.npm ?? provider.api.npm;
2450
3397
  if (!npm) {
2451
3398
  throw new RelayCoreError("UNSUPPORTED_MODEL", `Model "${modelId}" has no SDK provider package \u2014 refresh the provider's models in relay-ai ui.`, { providerId, routeId });
@@ -2469,10 +3416,11 @@ async function createRelayModel(routeId) {
2469
3416
  headers: provider.api.headers,
2470
3417
  refreshToken: providerRefreshToken(provider.id, provider.authType, provider.authRef),
2471
3418
  useResponsesLite: model.useResponsesLite,
2472
- preferWebSockets: model.preferWebSockets
3419
+ preferWebSockets: model.preferWebSockets,
3420
+ ...options?.onDebug ? { onDebug: options.onDebug } : {}
2473
3421
  };
2474
3422
  try {
2475
- return await createLanguageModel(spec);
3423
+ return await finish(await createLanguageModel(spec));
2476
3424
  } catch (err) {
2477
3425
  if (isRelayCoreError(err)) throw err;
2478
3426
  throw new RelayCoreError(