@jacobbd/relay-ai 0.7.5 → 0.7.6

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.
@@ -11,7 +11,7 @@ import { join } from "path";
11
11
  // package.json
12
12
  var package_default = {
13
13
  name: "@jacobbd/relay-ai",
14
- version: "0.7.5",
14
+ version: "0.7.6",
15
15
  publishConfig: {
16
16
  access: "public"
17
17
  },
@@ -4315,6 +4315,240 @@ function maskGatewayModelId(aliasId) {
4315
4315
  return `anthropic-${reverseSegment(providerSlug)}__${reverseSegment(modelSuffix)}`;
4316
4316
  }
4317
4317
 
4318
+ // src/subagent-route-registry.ts
4319
+ import { randomUUID as randomUUID2 } from "crypto";
4320
+ var DEFAULT_TTL_MS = 5 * 6e4;
4321
+ var DEFAULT_MAX_ENTRIES = 1024;
4322
+ var ROUTE_MARKER_PATTERN = /(?:\n\n)?<relay-ai-subagent-route token="([0-9a-f-]{36})"\s*\/>/gi;
4323
+ function firstHeader(value) {
4324
+ const first = Array.isArray(value) ? value[0] : value;
4325
+ return typeof first === "string" && first.trim() ? first.trim() : void 0;
4326
+ }
4327
+ function extractClaudeSessionId(headers, body) {
4328
+ const headerSession = firstHeader(headers["x-claude-code-session-id"]);
4329
+ if (headerSession) return headerSession;
4330
+ const userId = body?.metadata?.user_id;
4331
+ if (typeof userId !== "string") return void 0;
4332
+ try {
4333
+ const parsed = JSON.parse(userId);
4334
+ return typeof parsed.session_id === "string" && parsed.session_id.trim() ? parsed.session_id.trim() : void 0;
4335
+ } catch {
4336
+ return void 0;
4337
+ }
4338
+ }
4339
+ function appendSubagentRouteMarker(prompt, token) {
4340
+ return `${prompt}
4341
+
4342
+ <relay-ai-subagent-route token="${token}"/>`;
4343
+ }
4344
+ function findMarkersInUserMessages(body) {
4345
+ if (!Array.isArray(body.messages)) return void 0;
4346
+ const tokens = [];
4347
+ const messages = [...body.messages];
4348
+ for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) {
4349
+ const message = body.messages[messageIndex];
4350
+ if (!message || message.role !== "user") continue;
4351
+ if (typeof message.content === "string") {
4352
+ const matches = [...message.content.matchAll(ROUTE_MARKER_PATTERN)];
4353
+ if (matches.length === 0) continue;
4354
+ tokens.push(...matches.flatMap((match) => match[1] ? [match[1]] : []));
4355
+ messages[messageIndex] = {
4356
+ ...message,
4357
+ content: message.content.replace(ROUTE_MARKER_PATTERN, "").trimEnd()
4358
+ };
4359
+ continue;
4360
+ }
4361
+ if (!Array.isArray(message.content)) continue;
4362
+ const content = [...message.content];
4363
+ for (let partIndex = 0; partIndex < message.content.length; partIndex++) {
4364
+ const part = message.content[partIndex];
4365
+ if (!part || part.type !== "text" || typeof part.text !== "string") continue;
4366
+ const matches = [...part.text.matchAll(ROUTE_MARKER_PATTERN)];
4367
+ if (matches.length === 0) continue;
4368
+ tokens.push(...matches.flatMap((match) => match[1] ? [match[1]] : []));
4369
+ content[partIndex] = {
4370
+ ...part,
4371
+ text: part.text.replace(ROUTE_MARKER_PATTERN, "").trimEnd()
4372
+ };
4373
+ }
4374
+ messages[messageIndex] = { ...message, content };
4375
+ }
4376
+ return tokens.length > 0 ? { tokens, body: { ...body, messages } } : void 0;
4377
+ }
4378
+ var SubagentRouteRegistry = class {
4379
+ entries = /* @__PURE__ */ new Map();
4380
+ ttlMs;
4381
+ maxEntries;
4382
+ now;
4383
+ constructor(options = {}) {
4384
+ this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
4385
+ this.maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
4386
+ this.now = options.now ?? Date.now;
4387
+ }
4388
+ register(sessionId, modelId) {
4389
+ this.cleanup();
4390
+ while (this.entries.size >= this.maxEntries) {
4391
+ const oldest = this.entries.keys().next().value;
4392
+ if (!oldest) break;
4393
+ this.entries.delete(oldest);
4394
+ }
4395
+ const token = randomUUID2();
4396
+ this.entries.set(token, { sessionId, modelId, createdAt: this.now() });
4397
+ return token;
4398
+ }
4399
+ consume(headers, body) {
4400
+ this.cleanup();
4401
+ if (!firstHeader(headers["x-claude-code-agent-id"])) return void 0;
4402
+ const sessionId = extractClaudeSessionId(headers, body);
4403
+ if (!sessionId) return void 0;
4404
+ const marked = findMarkersInUserMessages(body);
4405
+ if (!marked) return void 0;
4406
+ const token = [...marked.tokens].reverse().find((candidate) => this.entries.get(candidate)?.sessionId === sessionId);
4407
+ if (!token) return void 0;
4408
+ const entry = this.entries.get(token);
4409
+ this.entries.delete(token);
4410
+ return { modelId: entry.modelId, body: marked.body };
4411
+ }
4412
+ cleanup() {
4413
+ const cutoff = this.now() - this.ttlMs;
4414
+ for (const [token, entry] of this.entries) {
4415
+ if (entry.createdAt < cutoff) this.entries.delete(token);
4416
+ }
4417
+ }
4418
+ };
4419
+
4420
+ // src/subagent-model-routing.ts
4421
+ var CLAUDE_MODEL_FAMILIES = ["sonnet", "opus", "haiku", "fable"];
4422
+ var CLAUDE_MODEL_FAMILY_SET = new Set(CLAUDE_MODEL_FAMILIES);
4423
+ function isRecord(value) {
4424
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4425
+ }
4426
+ function claudeModelFamily(modelId) {
4427
+ const normalized = modelId.toLowerCase();
4428
+ if (!normalized.startsWith("claude-")) return void 0;
4429
+ return CLAUDE_MODEL_FAMILIES.find((family) => normalized.includes(family));
4430
+ }
4431
+ function isClaudeAgentTool(tool4) {
4432
+ if (tool4.name !== "Agent" || !isRecord(tool4.input_schema)) return false;
4433
+ const properties = tool4.input_schema.properties;
4434
+ if (!isRecord(properties)) return false;
4435
+ return ["description", "prompt", "subagent_type"].every((name) => isRecord(properties[name]));
4436
+ }
4437
+ var UnavailableSubagentModelError = class extends Error {
4438
+ constructor(selector, routing) {
4439
+ const visible = routing.models.slice(0, MAX_MODEL_CATALOG).map((model) => model.id);
4440
+ const omitted = routing.models.length - visible.length;
4441
+ const suffix = omitted > 0 ? `, and ${omitted} more` : "";
4442
+ super(
4443
+ `Subagent model "${selector}" is unavailable in this Relay AI session. Available model ids: ${visible.join(", ")}${suffix}.`
4444
+ );
4445
+ this.selector = selector;
4446
+ this.name = "UnavailableSubagentModelError";
4447
+ }
4448
+ selector;
4449
+ statusCode = 400;
4450
+ };
4451
+ function normalizeClaudeAgentInput(input, routing) {
4452
+ const source = isRecord(input) ? input : {};
4453
+ const normalized = { ...source };
4454
+ if (source.subagent_type === "fork") {
4455
+ return { input: normalized, decision: { kind: "fork" } };
4456
+ }
4457
+ const rawModel = source.model;
4458
+ if (rawModel == null) {
4459
+ normalized.model = routing.parentModelId;
4460
+ return {
4461
+ input: normalized,
4462
+ decision: { kind: "inherit", resolvedModelId: routing.parentModelId }
4463
+ };
4464
+ }
4465
+ if (typeof rawModel !== "string") {
4466
+ throw new UnavailableSubagentModelError(String(rawModel), routing);
4467
+ }
4468
+ const selector = rawModel.trim();
4469
+ if (selector === "" || selector === "inherit") {
4470
+ normalized.model = routing.parentModelId;
4471
+ return {
4472
+ input: normalized,
4473
+ decision: { kind: "inherit", resolvedModelId: routing.parentModelId }
4474
+ };
4475
+ }
4476
+ const exposed = routing.models.find((model) => model.id === selector);
4477
+ if (exposed) {
4478
+ normalized.model = exposed.id;
4479
+ return {
4480
+ input: normalized,
4481
+ decision: { kind: "explicit", resolvedModelId: exposed.id }
4482
+ };
4483
+ }
4484
+ const compatible = routing.models.find((model) => model.compatibilityIds.includes(selector));
4485
+ if (compatible) {
4486
+ normalized.model = compatible.id;
4487
+ return {
4488
+ input: normalized,
4489
+ decision: {
4490
+ kind: "compatibility",
4491
+ requestedModelId: selector,
4492
+ resolvedModelId: compatible.id
4493
+ }
4494
+ };
4495
+ }
4496
+ if (CLAUDE_MODEL_FAMILY_SET.has(selector)) {
4497
+ const family = selector;
4498
+ const nativeModel = routing.models.find((model) => model.family === family);
4499
+ const resolvedModelId = nativeModel?.id ?? routing.parentModelId;
4500
+ normalized.model = resolvedModelId;
4501
+ return {
4502
+ input: normalized,
4503
+ decision: nativeModel ? { kind: "family", requestedModelId: family, resolvedModelId } : { kind: "family-fallback", requestedModelId: family, resolvedModelId }
4504
+ };
4505
+ }
4506
+ throw new UnavailableSubagentModelError(selector, routing);
4507
+ }
4508
+ function prepareClaudeAgentInput(input, routing) {
4509
+ const normalized = normalizeClaudeAgentInput(input, routing);
4510
+ const decision = normalized.decision;
4511
+ if (decision.kind === "fork") return normalized;
4512
+ if (!routing.registerSubagentRoute) return normalized;
4513
+ const prompt = normalized.input.prompt;
4514
+ if (typeof prompt !== "string") return normalized;
4515
+ const token = routing.registerSubagentRoute(decision.resolvedModelId);
4516
+ const clientInput = { ...normalized.input };
4517
+ const target = routing.models.find((model) => model.id === decision.resolvedModelId);
4518
+ if (target?.family) clientInput.model = target.family;
4519
+ else delete clientInput.model;
4520
+ clientInput.prompt = appendSubagentRouteMarker(prompt, token);
4521
+ return { input: clientInput, decision };
4522
+ }
4523
+ function augmentClaudeAgentTool(tool4, routing) {
4524
+ const inputSchema = isRecord(tool4.input_schema) ? tool4.input_schema : {};
4525
+ const properties = isRecord(inputSchema.properties) ? inputSchema.properties : {};
4526
+ const originalModel = isRecord(properties.model) ? properties.model : {};
4527
+ const smallCatalog = routing.models.length <= MAX_MODEL_CATALOG;
4528
+ const modelProperty = {
4529
+ ...originalModel,
4530
+ type: "string"
4531
+ };
4532
+ if (smallCatalog) {
4533
+ const originalEnum = Array.isArray(originalModel.enum) ? originalModel.enum.filter((value) => typeof value === "string") : CLAUDE_MODEL_FAMILIES;
4534
+ modelProperty.enum = [.../* @__PURE__ */ new Set([...originalEnum, ...routing.models.map((model) => model.id)])];
4535
+ } else {
4536
+ delete modelProperty.enum;
4537
+ }
4538
+ const guidance = smallCatalog ? `Relay AI subagent model routing (default: ${routing.parentModelId}). ` + routing.models.map((model) => `${model.displayName}: ${model.id}`).join("; ") : `Relay AI subagent model routing (default: ${routing.parentModelId}). Other explicit model values must be exact ids from the current session catalog.`;
4539
+ return {
4540
+ ...tool4,
4541
+ description: [tool4.description?.trim(), guidance].filter(Boolean).join("\n\n"),
4542
+ input_schema: {
4543
+ ...inputSchema,
4544
+ properties: {
4545
+ ...properties,
4546
+ model: modelProperty
4547
+ }
4548
+ }
4549
+ };
4550
+ }
4551
+
4318
4552
  // src/server/models.ts
4319
4553
  var CREATED_AT_ISO = "2025-01-01T00:00:00Z";
4320
4554
  var CREATED_AT_UNIX = 1735689600;
@@ -4368,6 +4602,52 @@ function exposedGatewayAliasId(model, opts) {
4368
4602
  const exposed = opts?.maskGatewayIds ? maskGatewayModelId(alias) : alias;
4369
4603
  return singleOneM ? `${stripOneMContextSuffix(exposed)}[1m]` : exposed;
4370
4604
  }
4605
+ function gatewayModelIdentity(model, models, opts) {
4606
+ const collisions = openAiIdCollisions(models);
4607
+ const ids = [];
4608
+ const modelIndex = models.indexOf(model);
4609
+ const firstBareIndex = models.findIndex((candidate) => candidate.id === model.id);
4610
+ if (modelIndex === firstBareIndex || modelIndex < 0) ids.push(model.id);
4611
+ const scopedId = openAiExposedId(model, collisions);
4612
+ if (scopedId !== model.id) ids.push(scopedId);
4613
+ const publicId = exposedGatewayAliasId(model, opts);
4614
+ if (publicId !== model.id) ids.push(publicId);
4615
+ const singleOneM = usesSingleOneMEntry(model, opts);
4616
+ if (singleOneM) {
4617
+ const bareModel = { ...model, id: stripOneMContextSuffix(model.id) };
4618
+ const rawBareAlias = gatewayAliasId(bareModel);
4619
+ const exposedBareAlias = opts?.maskGatewayIds ? maskGatewayModelId(rawBareAlias) : rawBareAlias;
4620
+ ids.push(
4621
+ stripOneMContextSuffix(model.id),
4622
+ rawBareAlias,
4623
+ `${rawBareAlias}[1m]`,
4624
+ exposedBareAlias,
4625
+ `${exposedBareAlias}[1m]`
4626
+ );
4627
+ }
4628
+ if (opts?.maskGatewayIds) {
4629
+ const rawAlias = gatewayAliasId(singleOneM ? { ...model, id: stripOneMContextSuffix(model.id) } : model);
4630
+ if (rawAlias !== publicId) ids.push(rawAlias);
4631
+ }
4632
+ return {
4633
+ publicId,
4634
+ compatibilityIds: [...new Set(ids)]
4635
+ };
4636
+ }
4637
+ function buildServerSubagentModelRouting(models, parentModel, opts) {
4638
+ const identities = models.map((model) => gatewayModelIdentity(model, models, opts));
4639
+ const parentIndex = models.indexOf(parentModel);
4640
+ const parentModelId = parentIndex >= 0 ? identities[parentIndex].publicId : exposedGatewayAliasId(parentModel, opts);
4641
+ return {
4642
+ parentModelId,
4643
+ models: models.map((model, index) => ({
4644
+ id: identities[index].publicId,
4645
+ compatibilityIds: identities[index].compatibilityIds,
4646
+ displayName: gatewayDisplayName(model, opts),
4647
+ family: model.modelFormat === "anthropic" ? claudeModelFamily(model.upstreamModelId ?? model.id) : void 0
4648
+ }))
4649
+ };
4650
+ }
4371
4651
  function gatewayDisplayName(model, opts) {
4372
4652
  const name = opts?.maskGatewayIds ? `${model.name} (${gatewayProviderLabel(model)})` : model.name;
4373
4653
  return usesSingleOneMEntry(model, opts) && !/\b1m$/i.test(name) ? `${name} 1M` : name;
@@ -4387,31 +4667,10 @@ function usesSingleOneMEntry(model, opts) {
4387
4667
  }
4388
4668
  function createGatewayModelCatalog(models, opts) {
4389
4669
  const byId = /* @__PURE__ */ new Map();
4390
- const collisions = openAiIdCollisions(models);
4391
4670
  for (const model of models) {
4392
- if (!byId.has(model.id)) byId.set(model.id, model);
4393
- const scopedId = openAiExposedId(model, collisions);
4394
- if (scopedId !== model.id) byId.set(scopedId, model);
4395
- const alias = exposedGatewayAliasId(model, opts);
4396
- if (alias !== model.id) byId.set(alias, model);
4397
- const singleOneM = usesSingleOneMEntry(model, opts);
4398
- if (singleOneM) {
4399
- const bareModel = { ...model, id: stripOneMContextSuffix(model.id) };
4400
- const rawBareAlias = gatewayAliasId(bareModel);
4401
- const exposedBareAlias = opts?.maskGatewayIds ? maskGatewayModelId(rawBareAlias) : rawBareAlias;
4402
- for (const compatibleId of [
4403
- stripOneMContextSuffix(model.id),
4404
- rawBareAlias,
4405
- `${rawBareAlias}[1m]`,
4406
- exposedBareAlias,
4407
- `${exposedBareAlias}[1m]`
4408
- ]) {
4409
- byId.set(compatibleId, model);
4410
- }
4411
- }
4412
- if (opts?.maskGatewayIds) {
4413
- const rawAlias = gatewayAliasId(singleOneM ? { ...model, id: stripOneMContextSuffix(model.id) } : model);
4414
- if (rawAlias !== alias) byId.set(rawAlias, model);
4671
+ const identity = gatewayModelIdentity(model, models, opts);
4672
+ for (const compatibleId of identity.compatibilityIds) {
4673
+ byId.set(compatibleId, model);
4415
4674
  }
4416
4675
  }
4417
4676
  return {
@@ -4611,10 +4870,10 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
4611
4870
  }
4612
4871
 
4613
4872
  // src/antigravity/anthropic-to-cloudcode.ts
4614
- import { randomUUID as randomUUID3 } from "crypto";
4873
+ import { randomUUID as randomUUID4 } from "crypto";
4615
4874
 
4616
4875
  // src/antigravity/request-adapter.ts
4617
- import { randomUUID as randomUUID2 } from "crypto";
4876
+ import { randomUUID as randomUUID3 } from "crypto";
4618
4877
  import { tool, jsonSchema } from "ai";
4619
4878
 
4620
4879
  // src/proxy-shared.ts
@@ -4724,6 +4983,43 @@ function serializeToolResultContent(content) {
4724
4983
  }
4725
4984
 
4726
4985
  // src/antigravity/request-adapter.ts
4986
+ var UNSUPPORTED_VOICE_MESSAGE = "Voice transcription isn\u2019t supported by Relay AI yet. Please type your message. Your coding session remains active.";
4987
+ var OMITTED_VOICE_TEXT = "[Voice recording omitted because transcription is not supported by Relay AI.]";
4988
+ function isSupportedImage(part) {
4989
+ return part.inlineData?.mimeType.toLowerCase().startsWith("image/") ?? false;
4990
+ }
4991
+ function isUnsupportedInlineData(part) {
4992
+ return !!part.inlineData && !isSupportedImage(part);
4993
+ }
4994
+ function sanitizeUnsupportedInlineData(ccReq) {
4995
+ const contents = ccReq.request?.contents ?? [];
4996
+ let latestUserIndex = -1;
4997
+ for (let i = contents.length - 1; i >= 0; i--) {
4998
+ if (contents[i].role === "user") {
4999
+ latestUserIndex = i;
5000
+ break;
5001
+ }
5002
+ }
5003
+ let latestUserTurnHasUnsupportedMedia = false;
5004
+ const sanitizedContents = contents.map((message, index) => ({
5005
+ ...message,
5006
+ parts: message.parts.map((part) => {
5007
+ if (!isUnsupportedInlineData(part)) return part;
5008
+ if (index === latestUserIndex) latestUserTurnHasUnsupportedMedia = true;
5009
+ return { text: OMITTED_VOICE_TEXT };
5010
+ })
5011
+ }));
5012
+ return {
5013
+ request: {
5014
+ ...ccReq,
5015
+ request: {
5016
+ ...ccReq.request,
5017
+ contents: sanitizedContents
5018
+ }
5019
+ },
5020
+ latestUserTurnHasUnsupportedMedia
5021
+ };
5022
+ }
4727
5023
  function tracePartChars(part) {
4728
5024
  if (typeof part.text === "string") return part.text.length;
4729
5025
  if (part.type !== "tool-result") return void 0;
@@ -4883,13 +5179,17 @@ function translateRequest(ccReq, options = {}) {
4883
5179
  }
4884
5180
  }
4885
5181
  } else if (part.inlineData) {
4886
- contentParts.push({
4887
- type: "image",
4888
- image: part.inlineData.data,
4889
- mimeType: part.inlineData.mimeType
4890
- });
5182
+ if (isSupportedImage(part)) {
5183
+ contentParts.push({
5184
+ type: "image",
5185
+ image: part.inlineData.data,
5186
+ mimeType: part.inlineData.mimeType
5187
+ });
5188
+ } else {
5189
+ contentParts.push({ type: "text", text: OMITTED_VOICE_TEXT });
5190
+ }
4891
5191
  } else if (part.functionCall) {
4892
- const id = "call_" + randomUUID2().replace(/-/g, "");
5192
+ const id = "call_" + randomUUID3().replace(/-/g, "");
4893
5193
  const name = part.functionCall.name;
4894
5194
  if (!nameToIdList.has(name)) nameToIdList.set(name, []);
4895
5195
  nameToIdList.get(name).push(id);
@@ -4902,7 +5202,7 @@ function translateRequest(ccReq, options = {}) {
4902
5202
  } else if (part.functionResponse) {
4903
5203
  const name = part.functionResponse.name;
4904
5204
  const idList = nameToIdList.get(name) || [];
4905
- const id = idList.shift() || "call_" + randomUUID2().replace(/-/g, "");
5205
+ const id = idList.shift() || "call_" + randomUUID3().replace(/-/g, "");
4906
5206
  toolResults.push({
4907
5207
  type: "tool-result",
4908
5208
  toolCallId: id,
@@ -5111,7 +5411,7 @@ function anthropicToCloudCode(body, realModelId, projectId) {
5111
5411
  }
5112
5412
  return {
5113
5413
  project: projectId,
5114
- requestId: randomUUID3(),
5414
+ requestId: randomUUID4(),
5115
5415
  model: realModelId,
5116
5416
  userAgent: ANTIGRAVITY_USER_AGENT2,
5117
5417
  requestType: "agent",
@@ -5121,7 +5421,7 @@ function anthropicToCloudCode(body, realModelId, projectId) {
5121
5421
  }
5122
5422
 
5123
5423
  // src/antigravity/cloudcode-to-anthropic.ts
5124
- import { randomUUID as randomUUID4 } from "crypto";
5424
+ import { randomUUID as randomUUID5 } from "crypto";
5125
5425
  function writeEvent(res, event, data) {
5126
5426
  res.write(`event: ${event}
5127
5427
  data: ${JSON.stringify(data)}
@@ -5211,7 +5511,7 @@ function closeBlock(res, state) {
5211
5511
  }
5212
5512
  async function streamCloudCodeToAnthropic(res, upstreamRes, model, log7) {
5213
5513
  const state = {
5214
- messageId: `msg_${randomUUID4().replace(/-/g, "").slice(0, 24)}`,
5514
+ messageId: `msg_${randomUUID5().replace(/-/g, "").slice(0, 24)}`,
5215
5515
  model,
5216
5516
  blockIdx: 0,
5217
5517
  textBlockOpen: false,
@@ -5300,7 +5600,7 @@ async function streamCloudCodeToAnthropic(res, upstreamRes, model, log7) {
5300
5600
  closeBlock(res, state);
5301
5601
  }
5302
5602
  for (const tc of state.toolCalls) {
5303
- const rawToolId = `toolu_${randomUUID4().replace(/-/g, "").slice(0, 16)}`;
5603
+ const rawToolId = `toolu_${randomUUID5().replace(/-/g, "").slice(0, 16)}`;
5304
5604
  const toolId = encodeToolUseId(rawToolId, tc.signature);
5305
5605
  writeEvent(res, "content_block_start", {
5306
5606
  type: "content_block_start",
@@ -5351,7 +5651,7 @@ async function streamCloudCodeToAnthropic(res, upstreamRes, model, log7) {
5351
5651
  }
5352
5652
  async function collectCloudCodeToAnthropic(upstreamRes, model, log7) {
5353
5653
  const text4 = await upstreamRes.text();
5354
- const messageId = `msg_${randomUUID4().replace(/-/g, "").slice(0, 24)}`;
5654
+ const messageId = `msg_${randomUUID5().replace(/-/g, "").slice(0, 24)}`;
5355
5655
  const content = [];
5356
5656
  let stopReason = "end_turn";
5357
5657
  let inputTokens = 0;
@@ -5380,7 +5680,7 @@ async function collectCloudCodeToAnthropic(upstreamRes, model, log7) {
5380
5680
  else content.push({ type: "text", text: part.text });
5381
5681
  } else if (part.functionCall && typeof part.functionCall === "object") {
5382
5682
  const fc = part.functionCall;
5383
- const rawToolId = `toolu_${randomUUID4().replace(/-/g, "").slice(0, 16)}`;
5683
+ const rawToolId = `toolu_${randomUUID5().replace(/-/g, "").slice(0, 16)}`;
5384
5684
  content.push({
5385
5685
  type: "tool_use",
5386
5686
  id: encodeToolUseId(rawToolId, signature ?? pendingThoughtSignature),
@@ -5413,7 +5713,7 @@ async function collectCloudCodeToAnthropic(upstreamRes, model, log7) {
5413
5713
  }
5414
5714
 
5415
5715
  // src/proxy.ts
5416
- import { randomUUID as randomUUID5 } from "crypto";
5716
+ import { randomUUID as randomUUID6 } from "crypto";
5417
5717
 
5418
5718
  // src/sdk-adapter.ts
5419
5719
  import { streamText, generateText, tool as tool2, jsonSchema as jsonSchema2 } from "ai";
@@ -5746,6 +6046,17 @@ function translateRequest2(body, npm, options) {
5746
6046
  if (options?.maxTools !== void 0 && upstreamTools.length > options.maxTools) {
5747
6047
  upstreamTools = upstreamTools.slice(0, options.maxTools);
5748
6048
  }
6049
+ let responseSubagentRouting;
6050
+ if (options?.subagentRouting) {
6051
+ const agentIndex = upstreamTools.findIndex((toolDefinition) => isClaudeAgentTool(toolDefinition));
6052
+ if (agentIndex >= 0) {
6053
+ upstreamTools = upstreamTools.map((toolDefinition, index) => index === agentIndex ? augmentClaudeAgentTool(
6054
+ toolDefinition,
6055
+ options.subagentRouting
6056
+ ) : toolDefinition);
6057
+ responseSubagentRouting = options.subagentRouting;
6058
+ }
6059
+ }
5749
6060
  const effort = anthropicEffortFromRequest(body) ?? options?.defaultEffort;
5750
6061
  let providerOptions = deepMergeProviderOptions(
5751
6062
  thinkingProviderOptions(npm),
@@ -5763,16 +6074,22 @@ function translateRequest2(body, npm, options) {
5763
6074
  toolChoice: translateToolChoice(body.tool_choice),
5764
6075
  maxOutputTokens: options?.openAiOAuth ? void 0 : body.max_tokens,
5765
6076
  temperature: body.temperature,
5766
- providerOptions
6077
+ providerOptions,
6078
+ subagentRouting: responseSubagentRouting
5767
6079
  };
5768
6080
  }
5769
- async function writeAnthropicStream(fullStream, modelId, write, log7, estimatedInputTokens = 0) {
6081
+ function logSubagentDecision(log7, decision) {
6082
+ if (!log7 || decision.kind === "fork" || decision.kind === "explicit") return;
6083
+ log7(() => decision.kind === "family-fallback" ? `sdk Agent model "${decision.requestedModelId}" unavailable; using parent ${decision.resolvedModelId}` : `sdk Agent model ${decision.kind}: ${"requestedModelId" in decision ? `${decision.requestedModelId} -> ` : ""}${decision.resolvedModelId}`);
6084
+ }
6085
+ async function writeAnthropicStream(fullStream, modelId, write, log7, estimatedInputTokens = 0, subagentRouting) {
5770
6086
  const messageId = "msg_" + Date.now();
5771
6087
  let blockIndex = -1;
5772
6088
  let started = false;
5773
6089
  let openType = null;
5774
6090
  let pendingThinkingSig;
5775
6091
  const idToBlock = /* @__PURE__ */ new Map();
6092
+ const bufferedAgentCalls = /* @__PURE__ */ new Map();
5776
6093
  let finishReason = "end_turn";
5777
6094
  let usage = { input_tokens: estimatedInputTokens, output_tokens: 0 };
5778
6095
  const emit = (event, data) => write(sseChunk(event, data));
@@ -5855,9 +6172,13 @@ async function writeAnthropicStream(fullStream, modelId, write, log7, estimatedI
5855
6172
  input: {}
5856
6173
  });
5857
6174
  idToBlock.set(part.id ?? "", blockIndex);
6175
+ if (subagentRouting && part.toolName === "Agent") {
6176
+ bufferedAgentCalls.set(part.id ?? "", { blockIndex });
6177
+ }
5858
6178
  break;
5859
6179
  }
5860
6180
  case "tool-input-delta":
6181
+ if (bufferedAgentCalls.has(part.id ?? "")) break;
5861
6182
  emit("content_block_delta", {
5862
6183
  type: "content_block_delta",
5863
6184
  index: idToBlock.get(part.id ?? "") ?? blockIndex,
@@ -5868,11 +6189,53 @@ async function writeAnthropicStream(fullStream, modelId, write, log7, estimatedI
5868
6189
  break;
5869
6190
  case "tool-call": {
5870
6191
  finishReason = "tool_use";
5871
- if (!idToBlock.has(part.toolCallId ?? "") && openType !== "tool") {
6192
+ const toolCallId = part.toolCallId ?? "";
6193
+ if (subagentRouting && part.toolName === "Agent") {
6194
+ try {
6195
+ const normalized = prepareClaudeAgentInput(part.input, subagentRouting);
6196
+ logSubagentDecision(log7, normalized.decision);
6197
+ const buffered = bufferedAgentCalls.get(toolCallId);
6198
+ if (!buffered && !idToBlock.has(toolCallId)) {
6199
+ const sig = grabRoundTripSignature(part);
6200
+ openBlock("tool", {
6201
+ type: "tool_use",
6202
+ id: encodeToolUseId(toolCallId, sig),
6203
+ name: part.toolName,
6204
+ input: {}
6205
+ });
6206
+ idToBlock.set(toolCallId, blockIndex);
6207
+ }
6208
+ emit("content_block_delta", {
6209
+ type: "content_block_delta",
6210
+ index: buffered?.blockIndex ?? idToBlock.get(toolCallId) ?? blockIndex,
6211
+ delta: {
6212
+ type: "input_json_delta",
6213
+ partial_json: JSON.stringify(stripNullInputs(normalized.input))
6214
+ }
6215
+ });
6216
+ bufferedAgentCalls.delete(toolCallId);
6217
+ } catch (error) {
6218
+ if (error instanceof UnavailableSubagentModelError) {
6219
+ bufferedAgentCalls.delete(toolCallId);
6220
+ closeOpen();
6221
+ emit("error", {
6222
+ type: "error",
6223
+ error: {
6224
+ type: anthropicErrorType(error.statusCode),
6225
+ message: error.message
6226
+ }
6227
+ });
6228
+ return;
6229
+ }
6230
+ throw error;
6231
+ }
6232
+ break;
6233
+ }
6234
+ if (!idToBlock.has(toolCallId) && openType !== "tool") {
5872
6235
  const sig = grabRoundTripSignature(part);
5873
6236
  openBlock("tool", {
5874
6237
  type: "tool_use",
5875
- id: encodeToolUseId(part.toolCallId ?? "", sig),
6238
+ id: encodeToolUseId(toolCallId, sig),
5876
6239
  name: part.toolName,
5877
6240
  input: {}
5878
6241
  });
@@ -5900,6 +6263,7 @@ async function writeAnthropicStream(fullStream, modelId, write, log7, estimatedI
5900
6263
  const errMsg = e?.message || (typeof part.error === "string" ? part.error : JSON.stringify(e?.data ?? part.error));
5901
6264
  const errorType = anthropicErrorType(upstreamHttpStatus(part.error, errMsg));
5902
6265
  log7?.(() => `sdk stream error (${errorType}): ${errMsg}`);
6266
+ bufferedAgentCalls.clear();
5903
6267
  closeOpen();
5904
6268
  emit("error", { type: "error", error: { type: errorType, message: errMsg } });
5905
6269
  return;
@@ -5914,7 +6278,8 @@ async function writeAnthropicStream(fullStream, modelId, write, log7, estimatedI
5914
6278
  emit("message_stop", { type: "message_stop" });
5915
6279
  }
5916
6280
  async function streamAnthropicResponse(model, params, modelId, write, log7, estimatedInputTokens = 0) {
5917
- const result = streamText({ model, ...params, onError: () => {
6281
+ const { subagentRouting, ...providerParams } = params;
6282
+ const result = streamText({ model, ...providerParams, onError: () => {
5918
6283
  } });
5919
6284
  Promise.resolve(result.text).catch(() => {
5920
6285
  });
@@ -5931,22 +6296,24 @@ async function streamAnthropicResponse(model, params, modelId, write, log7, esti
5931
6296
  modelId,
5932
6297
  write,
5933
6298
  log7,
5934
- estimatedInputTokens
6299
+ estimatedInputTokens,
6300
+ subagentRouting
5935
6301
  );
5936
6302
  }
5937
6303
  async function generateAnthropicResponse(model, params, modelId, options) {
6304
+ const { subagentRouting, ...providerParams } = params;
5938
6305
  let text4;
5939
6306
  let toolCalls;
5940
6307
  let finishReason;
5941
6308
  let usage;
5942
6309
  if (options?.forceStream) {
5943
- const r = streamText({ model, ...params, onError: () => {
6310
+ const r = streamText({ model, ...providerParams, onError: () => {
5944
6311
  } });
5945
6312
  Promise.resolve(r.toolResults).catch(() => {
5946
6313
  });
5947
6314
  [text4, toolCalls, finishReason, usage] = await Promise.all([r.text, r.toolCalls, r.finishReason, r.usage]);
5948
6315
  } else {
5949
- const r = await generateText({ model, ...params });
6316
+ const r = await generateText({ model, ...providerParams });
5950
6317
  ({ text: text4, toolCalls, finishReason, usage } = r);
5951
6318
  }
5952
6319
  return {
@@ -5956,12 +6323,20 @@ async function generateAnthropicResponse(model, params, modelId, options) {
5956
6323
  model: modelId,
5957
6324
  content: [
5958
6325
  ...text4 ? [{ type: "text", text: text4 }] : [],
5959
- ...toolCalls.map((tc) => ({
5960
- type: "tool_use",
5961
- id: encodeToolUseId(tc.toolCallId, grabRoundTripSignature(tc)),
5962
- name: tc.toolName,
5963
- input: stripNullInputs(tc.input)
5964
- }))
6326
+ ...toolCalls.map((tc) => {
6327
+ let input = tc.input;
6328
+ if (subagentRouting && tc.toolName === "Agent") {
6329
+ const normalized = prepareClaudeAgentInput(tc.input, subagentRouting);
6330
+ logSubagentDecision(options?.log, normalized.decision);
6331
+ input = normalized.input;
6332
+ }
6333
+ return {
6334
+ type: "tool_use",
6335
+ id: encodeToolUseId(tc.toolCallId, grabRoundTripSignature(tc)),
6336
+ name: tc.toolName,
6337
+ input: stripNullInputs(input)
6338
+ };
6339
+ })
5965
6340
  ],
5966
6341
  stop_reason: finishReason === "tool-calls" ? "tool_use" : "end_turn",
5967
6342
  usage: { input_tokens: usage?.inputTokens ?? 0, output_tokens: usage?.outputTokens ?? 0 }
@@ -6008,6 +6383,21 @@ function aliasModelId(realId, providerId) {
6008
6383
  const sanitized = providerId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
6009
6384
  return `anthropic-${sanitized}__${realId}`;
6010
6385
  }
6386
+ function buildProxySubagentModelRouting(routes, parentRoute) {
6387
+ const publicId = (route) => route.gatewayAliasId ?? route.aliasId;
6388
+ return {
6389
+ parentModelId: publicId(parentRoute),
6390
+ models: routes.map((route) => ({
6391
+ id: publicId(route),
6392
+ compatibilityIds: [.../* @__PURE__ */ new Set([
6393
+ ...routeLookupIds(route.aliasId),
6394
+ ...route.gatewayAliasId ? routeLookupIds(route.gatewayAliasId) : []
6395
+ ])],
6396
+ displayName: route.displayName,
6397
+ family: route.modelFormat === "anthropic" ? claudeModelFamily(route.realModelId) : void 0
6398
+ }))
6399
+ };
6400
+ }
6011
6401
  function lookupRoute(byAlias, id) {
6012
6402
  for (const key of routeLookupIds(id)) {
6013
6403
  const route = byAlias.get(key);
@@ -6016,13 +6406,14 @@ function lookupRoute(byAlias, id) {
6016
6406
  return void 0;
6017
6407
  }
6018
6408
  function startProxyCatalog(routes, defaultAliasId, debug = false) {
6019
- const proxyToken = randomUUID5();
6409
+ const proxyToken = randomUUID6();
6020
6410
  silenceSdkWarnings();
6021
6411
  if (routes.length === 0) {
6022
6412
  return Promise.reject(new Error("Proxy catalog requires at least one route"));
6023
6413
  }
6024
6414
  const byAlias = new Map(routes.map((r) => [r.aliasId, r]));
6025
6415
  const defaultRoute = byAlias.get(defaultAliasId) ?? routes[0];
6416
+ const subagentRouteRegistry = new SubagentRouteRegistry();
6026
6417
  const plog = makeProxyLog(debug);
6027
6418
  const onRejection = (reason) => {
6028
6419
  plog(() => `Unhandled Rejection: ${reason instanceof Error ? reason.stack || reason.message : String(reason)}`);
@@ -6076,9 +6467,12 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
6076
6467
  anthropicError(res, 400, "Invalid JSON body");
6077
6468
  return;
6078
6469
  }
6470
+ const correlatedSubagent = subagentRouteRegistry.consume(req.headers, anthropicBody);
6471
+ if (correlatedSubagent) anthropicBody = correlatedSubagent.body;
6079
6472
  const originalModel = anthropicBody.model;
6080
6473
  const clientWantsStream = Boolean(anthropicBody.stream);
6081
- const route = lookupRoute(byAlias, originalModel) ?? defaultRoute;
6474
+ const correlatedRoute = correlatedSubagent ? routes.find((candidate) => (candidate.gatewayAliasId ?? candidate.aliasId) === correlatedSubagent.modelId) : void 0;
6475
+ const route = correlatedRoute ?? lookupRoute(byAlias, originalModel) ?? defaultRoute;
6082
6476
  const apiKey = route.apiKey;
6083
6477
  const upstreamUrl = route.upstreamUrl;
6084
6478
  plog(
@@ -6134,10 +6528,16 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
6134
6528
  }
6135
6529
  if (usesSdkAdapter) {
6136
6530
  const openAiOAuth = route.npm === "@ai-sdk/openai" && route.authType === "oauth";
6531
+ const subagentRouting = buildProxySubagentModelRouting(routes, route);
6532
+ const sessionId = extractClaudeSessionId(req.headers, anthropicBody);
6533
+ if (sessionId) {
6534
+ subagentRouting.registerSubagentRoute = (modelId) => subagentRouteRegistry.register(sessionId, modelId);
6535
+ }
6137
6536
  const params = translateRequest2(anthropicBody, route.npm, {
6138
6537
  openAiOAuth,
6139
6538
  maxTools: maxToolsForNpm(route.npm),
6140
6539
  onDebug: (msg) => plog(() => msg),
6540
+ subagentRouting,
6141
6541
  reasoningMetadata: {
6142
6542
  providerId: route.providerId,
6143
6543
  apiBaseUrl: route.baseURL,
@@ -6185,7 +6585,7 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
6185
6585
  model,
6186
6586
  params,
6187
6587
  originalModel,
6188
- { forceStream: openAiOAuth }
6588
+ { forceStream: openAiOAuth, log: plog }
6189
6589
  );
6190
6590
  sendJson(res, 200, anthropicResponse);
6191
6591
  }
@@ -7926,6 +8326,14 @@ function injectRelayModels(fixture, routes, templateKey) {
7926
8326
  }]
7927
8327
  }
7928
8328
  ];
8329
+ for (const entry of Object.values(result.models)) {
8330
+ const mimeTypes = entry.supportedMimeTypes;
8331
+ if (!mimeTypes || typeof mimeTypes !== "object" || Array.isArray(mimeTypes)) continue;
8332
+ entry.supportedMimeTypes = Object.fromEntries(
8333
+ Object.entries(mimeTypes).filter(([mime]) => !mime.toLowerCase().includes("audio/"))
8334
+ );
8335
+ }
8336
+ result.audioTranscriptionModelIds = [];
7929
8337
  return result;
7930
8338
  }
7931
8339
  if (!result.agentModelSorts?.[0]?.groups?.[0]) {
@@ -9921,8 +10329,9 @@ async function startServer(options) {
9921
10329
  silenceSdkWarnings();
9922
10330
  const languageModelCache = /* @__PURE__ */ new Map();
9923
10331
  const plog = makeServerLog(options.debugLogPath);
10332
+ const subagentRouteRegistry = new SubagentRouteRegistry();
9924
10333
  const server = createServer2((req, res) => {
9925
- void routeRequest(req, res, options, languageModelCache, plog);
10334
+ void routeRequest(req, res, options, languageModelCache, plog, subagentRouteRegistry);
9926
10335
  });
9927
10336
  await new Promise((resolve, reject) => {
9928
10337
  server.once("error", reject);
@@ -9945,7 +10354,7 @@ async function startServer(options) {
9945
10354
  })
9946
10355
  };
9947
10356
  }
9948
- async function routeRequest(req, res, options, modelCache, plog) {
10357
+ async function routeRequest(req, res, options, modelCache, plog, subagentRouteRegistry) {
9949
10358
  try {
9950
10359
  const pathname = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`).pathname;
9951
10360
  plog(`${req.method} ${pathname}`);
@@ -9970,7 +10379,7 @@ async function routeRequest(req, res, options, modelCache, plog) {
9970
10379
  return;
9971
10380
  }
9972
10381
  if (req.method === "POST" && pathname === "/anthropic/v1/messages") {
9973
- await handleAnthropicMessages(req, res, options, modelCache, plog);
10382
+ await handleAnthropicMessages(req, res, options, modelCache, plog, subagentRouteRegistry);
9974
10383
  return;
9975
10384
  }
9976
10385
  if (req.method === "POST" && pathname === "/openai/v1/chat/completions") {
@@ -9982,13 +10391,16 @@ async function routeRequest(req, res, options, modelCache, plog) {
9982
10391
  sendJson(res, 500, { error: { message: err instanceof Error ? err.message : String(err) } });
9983
10392
  }
9984
10393
  }
9985
- async function handleAnthropicMessages(req, res, options, modelCache, plog) {
9986
- const body = await readJson(req);
10394
+ async function handleAnthropicMessages(req, res, options, modelCache, plog, subagentRouteRegistry) {
10395
+ let body = await readJson(req);
9987
10396
  if (!body) {
9988
10397
  sendJson(res, 400, { error: { message: "Invalid JSON body" } });
9989
10398
  return;
9990
10399
  }
9991
- const model = lookupModel(res, options.catalog, body.model);
10400
+ const correlatedSubagent = subagentRouteRegistry.consume(req.headers, body);
10401
+ if (correlatedSubagent) body = correlatedSubagent.body;
10402
+ const requestedModelId = correlatedSubagent?.modelId ?? body.model;
10403
+ const model = lookupModel(res, options.catalog, requestedModelId);
9992
10404
  if (!model) {
9993
10405
  plog(`model not found: ${body.model}`);
9994
10406
  return;
@@ -10047,10 +10459,20 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
10047
10459
  if (npmMaxTools !== void 0 && toolCount > npmMaxTools) {
10048
10460
  plog(`tools truncated: ${toolCount} \u2192 ${npmMaxTools} (provider limit)`);
10049
10461
  }
10462
+ const subagentRouting = buildServerSubagentModelRouting(
10463
+ options.catalog.list(),
10464
+ model,
10465
+ options.gateway
10466
+ );
10467
+ const sessionId = extractClaudeSessionId(req.headers, body);
10468
+ if (sessionId) {
10469
+ subagentRouting.registerSubagentRoute = (modelId) => subagentRouteRegistry.register(sessionId, modelId);
10470
+ }
10050
10471
  const params = translateRequest2(body, model.npm, {
10051
10472
  defaultEffort: anthropicEffortFromRequest(body) ? void 0 : model.defaultEffort,
10052
10473
  openAiOAuth: model.npm === "@ai-sdk/openai" && model.authType === "oauth",
10053
10474
  onDebug: plog,
10475
+ subagentRouting,
10054
10476
  reasoningMetadata: {
10055
10477
  providerId: model.providerId,
10056
10478
  apiBaseUrl: model.apiBaseUrl,
@@ -10076,12 +10498,17 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
10076
10498
  params,
10077
10499
  responseModelId,
10078
10500
  (chunk) => res.write(chunk),
10079
- void 0,
10501
+ plog,
10080
10502
  estimateAnthropicInputTokens(body)
10081
10503
  );
10082
10504
  res.end();
10083
10505
  } else {
10084
- const anthropicResponse = await generateAnthropicResponse(languageModel, params, responseModelId);
10506
+ const anthropicResponse = await generateAnthropicResponse(
10507
+ languageModel,
10508
+ params,
10509
+ responseModelId,
10510
+ { log: plog }
10511
+ );
10085
10512
  sendJson(res, 200, anthropicResponse);
10086
10513
  }
10087
10514
  } catch (err) {
@@ -12093,6 +12520,8 @@ export {
12093
12520
  splitToolUseId,
12094
12521
  encodeToolUseId,
12095
12522
  serializeToolResultContent,
12523
+ UNSUPPORTED_VOICE_MESSAGE,
12524
+ sanitizeUnsupportedInlineData,
12096
12525
  summarizeSdkRequestForTrace,
12097
12526
  translateRequest,
12098
12527
  formatUpstreamErrorTrace,
@@ -12162,4 +12591,4 @@ export {
12162
12591
  supportsClaudeTransparentMode,
12163
12592
  buildHttpProxyRoutes
12164
12593
  };
12165
- //# sourceMappingURL=chunk-HEMJFOXG.js.map
12594
+ //# sourceMappingURL=chunk-GHSURQOK.js.map