@jacobbd/relay-ai 0.9.7 → 0.10.0

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.
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getTemplateById,
4
4
  init_provider_templates
5
- } from "./chunk-NYKVDBQC.js";
5
+ } from "./chunk-P4IS6537.js";
6
6
 
7
7
  // src/constants.ts
8
8
  import { homedir } from "os";
@@ -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.9.7",
14
+ version: "0.10.0",
15
15
  publishConfig: {
16
16
  access: "public"
17
17
  },
@@ -6326,7 +6326,7 @@ async function collectCloudCodeToAnthropic(upstreamRes, model, log7) {
6326
6326
  }
6327
6327
 
6328
6328
  // src/proxy.ts
6329
- import { randomUUID as randomUUID5 } from "crypto";
6329
+ import { randomUUID as randomUUID6 } from "crypto";
6330
6330
 
6331
6331
  // src/sdk-adapter.ts
6332
6332
  import { streamText, generateText, tool, jsonSchema } from "ai";
@@ -6688,6 +6688,7 @@ function translateRequest(body, npm, options) {
6688
6688
  maxOutputTokens: options?.openAiOAuth ? void 0 : body.max_tokens,
6689
6689
  temperature: body.temperature,
6690
6690
  providerOptions,
6691
+ headers: options?.requestHeaders,
6691
6692
  subagentRouting: responseSubagentRouting
6692
6693
  };
6693
6694
  }
@@ -6956,6 +6957,109 @@ async function generateAnthropicResponse(model, params, modelId, options) {
6956
6957
  };
6957
6958
  }
6958
6959
 
6960
+ // src/opencode-session.ts
6961
+ import { randomUUID as randomUUID5 } from "crypto";
6962
+ var OPENCODE_SESSION_HEADER = "x-opencode-session";
6963
+ var MAX_OPENCODE_SESSION_LENGTH = 256;
6964
+ var RELAY_USER_AGENT = `relay-ai/${VERSION}`;
6965
+ var NATIVE_CONVERSATION_HEADERS = [
6966
+ "x-claude-code-session-id",
6967
+ "session_id",
6968
+ "session-id",
6969
+ "x-session-id",
6970
+ "thread_id",
6971
+ "thread-id",
6972
+ "x-thread-id",
6973
+ "conversation_id",
6974
+ "conversation-id",
6975
+ "x-conversation-id"
6976
+ ];
6977
+ function sanitizeSessionId(value) {
6978
+ if (typeof value !== "string") return void 0;
6979
+ const normalized = value.trim();
6980
+ if (!normalized || normalized.length > MAX_OPENCODE_SESSION_LENGTH) return void 0;
6981
+ if (/[\u0000-\u001f\u007f\r\n]/.test(normalized)) return void 0;
6982
+ return normalized;
6983
+ }
6984
+ function headerValue(headers, name) {
6985
+ if (!headers) return void 0;
6986
+ if (headers instanceof Headers) {
6987
+ return sanitizeSessionId(headers.get(name));
6988
+ }
6989
+ const lowerName = name.toLowerCase();
6990
+ for (const [key, value] of Object.entries(headers)) {
6991
+ if (key.toLowerCase() !== lowerName) continue;
6992
+ const first = Array.isArray(value) ? value[0] : value;
6993
+ return sanitizeSessionId(first);
6994
+ }
6995
+ return void 0;
6996
+ }
6997
+ function metadataSessionId(body) {
6998
+ if (!body || typeof body !== "object") return void 0;
6999
+ const record = body;
7000
+ const metadata = record.metadata;
7001
+ if (metadata && typeof metadata === "object") {
7002
+ const metadataRecord = metadata;
7003
+ const direct = sanitizeSessionId(metadataRecord.session_id ?? metadataRecord.sessionId);
7004
+ if (direct) return direct;
7005
+ const userId = metadataRecord.user_id;
7006
+ if (typeof userId === "string") {
7007
+ try {
7008
+ const parsed = JSON.parse(userId);
7009
+ const parsedId = sanitizeSessionId(parsed.session_id ?? parsed.sessionId);
7010
+ if (parsedId) return parsedId;
7011
+ } catch {
7012
+ }
7013
+ }
7014
+ }
7015
+ return sanitizeSessionId(record.session_id ?? record.sessionId ?? record.thread_id ?? record.threadId);
7016
+ }
7017
+ function extractConversationId(headers, body) {
7018
+ const explicit = headerValue(headers, OPENCODE_SESSION_HEADER);
7019
+ if (explicit) return explicit;
7020
+ for (const name of NATIVE_CONVERSATION_HEADERS) {
7021
+ const native = headerValue(headers, name);
7022
+ if (native) return native;
7023
+ }
7024
+ return metadataSessionId(body);
7025
+ }
7026
+ function isOpenCodeGoEndpoint(providerId, endpoint) {
7027
+ const id = providerId?.trim().toLowerCase();
7028
+ if (id === "go" || id === "opencode-go") return true;
7029
+ if (!endpoint) return false;
7030
+ try {
7031
+ const url = new URL(endpoint);
7032
+ if (url.hostname.toLowerCase() !== "opencode.ai") return false;
7033
+ return url.pathname.split("/").some((segment) => segment.toLowerCase() === "go");
7034
+ } catch {
7035
+ return false;
7036
+ }
7037
+ }
7038
+ function mergeHeaders(base, overrides) {
7039
+ const result = {};
7040
+ for (const [key, value] of Object.entries(base ?? {})) {
7041
+ if (typeof value === "string") result[key] = value;
7042
+ }
7043
+ for (const [key, value] of Object.entries(overrides ?? {})) {
7044
+ for (const existing of Object.keys(result)) {
7045
+ if (existing.toLowerCase() === key.toLowerCase()) delete result[existing];
7046
+ }
7047
+ result[key] = value;
7048
+ }
7049
+ return result;
7050
+ }
7051
+ function openCodeGoHeaders(providerId, endpoint, sessionId, baseHeaders, options) {
7052
+ if (!isOpenCodeGoEndpoint(providerId, endpoint)) return void 0;
7053
+ let normalizedSessionId = sanitizeSessionId(sessionId);
7054
+ if (!normalizedSessionId && (options?.generateFallbackSession ?? true)) {
7055
+ normalizedSessionId = `relay-${randomUUID5()}`;
7056
+ }
7057
+ return mergeHeaders(baseHeaders, {
7058
+ "User-Agent": RELAY_USER_AGENT,
7059
+ ...normalizedSessionId ? { [OPENCODE_SESSION_HEADER]: normalizedSessionId } : {}
7060
+ });
7061
+ }
7062
+
6959
7063
  // src/proxy.ts
6960
7064
  function appendSecureLog(logPath, line) {
6961
7065
  const redacted = redactTraceLine(line);
@@ -7019,7 +7123,7 @@ function lookupRoute(byAlias, id) {
7019
7123
  return void 0;
7020
7124
  }
7021
7125
  function startProxyCatalog(routes, defaultAliasId, debug = false) {
7022
- const proxyToken = randomUUID5();
7126
+ const proxyToken = randomUUID6();
7023
7127
  silenceSdkWarnings();
7024
7128
  if (routes.length === 0) {
7025
7129
  return Promise.reject(new Error("Proxy catalog requires at least one route"));
@@ -7102,6 +7206,12 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
7102
7206
  const forwardBody = { ...anthropicBody, model: route.realModelId };
7103
7207
  const targetUrl = `${upstreamUrl}/v1/messages`;
7104
7208
  const isOAuth = route.authType === "oauth";
7209
+ const upstreamHeaders = openCodeGoHeaders(
7210
+ route.providerId,
7211
+ route.baseURL ?? upstreamUrl,
7212
+ extractConversationId(req.headers, anthropicBody),
7213
+ route.headers
7214
+ ) ?? route.headers;
7105
7215
  let effectiveBeta = inboundBeta;
7106
7216
  let claudeCodeSessionId;
7107
7217
  if (isOAuth) {
@@ -7126,7 +7236,7 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
7126
7236
  isOAuth ? "oauth" : "api",
7127
7237
  (message) => plog(message),
7128
7238
  claudeCodeSessionId,
7129
- route.headers,
7239
+ upstreamHeaders,
7130
7240
  route.refreshToken,
7131
7241
  (refreshed) => {
7132
7242
  route.apiKey = refreshed;
@@ -7143,6 +7253,12 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
7143
7253
  const openAiOAuth = route.npm === "@ai-sdk/openai" && route.authType === "oauth";
7144
7254
  const subagentRouting = buildProxySubagentModelRouting(routes, route);
7145
7255
  const sessionId = extractClaudeSessionId(req.headers, anthropicBody);
7256
+ const requestHeaders = openCodeGoHeaders(
7257
+ route.providerId,
7258
+ route.baseURL ?? upstreamUrl,
7259
+ extractConversationId(req.headers, anthropicBody),
7260
+ route.headers
7261
+ );
7146
7262
  if (sessionId) {
7147
7263
  subagentRouting.registerSubagentRoute = (modelId) => subagentRouteRegistry.register(sessionId, modelId);
7148
7264
  }
@@ -7151,6 +7267,7 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
7151
7267
  maxTools: maxToolsForNpm(route.npm),
7152
7268
  onDebug: (msg) => plog(() => msg),
7153
7269
  subagentRouting,
7270
+ ...requestHeaders ? { requestHeaders } : {},
7154
7271
  reasoningMetadata: {
7155
7272
  providerId: route.providerId,
7156
7273
  apiBaseUrl: route.baseURL,
@@ -8768,14 +8885,9 @@ function evaluateAgySwitchCompatibility(opts) {
8768
8885
  };
8769
8886
  }
8770
8887
  if (opts.version && !KNOWN_COMPATIBLE_AGY_VERSIONS.has(opts.version)) {
8771
- return {
8772
- mode: "single-model",
8773
- validatedSwitchSlotCount: validation.switchSlots.length,
8774
- warnings: [
8775
- ...warnings,
8776
- `Unvalidated AGY version ${opts.version}; falling back to single-model mode for maximum stability.`
8777
- ]
8778
- };
8888
+ warnings.push(
8889
+ `AGY version ${opts.version} is not in the explicitly validated set, but its slot config shape matches; multi-model switching is enabled.`
8890
+ );
8779
8891
  } else if (!opts.version && !opts.versionReadError) {
8780
8892
  warnings.push("AGY version is unknown; fixture shape matches, so multi-model switching remains enabled.");
8781
8893
  }
@@ -11065,7 +11177,7 @@ import { createServer as createServer2 } from "http";
11065
11177
 
11066
11178
  // src/openai-adapter.ts
11067
11179
  import { tool as tool2, jsonSchema as jsonSchema2, streamText as streamText2, generateText as generateText2 } from "ai";
11068
- function translateOpenAiRequest(body) {
11180
+ function translateOpenAiRequest(body, requestHeaders) {
11069
11181
  const toolNameById = /* @__PURE__ */ new Map();
11070
11182
  for (const msg of body.messages) {
11071
11183
  if (msg.role === "assistant" && msg.tool_calls) {
@@ -11144,7 +11256,8 @@ function translateOpenAiRequest(body) {
11144
11256
  tools,
11145
11257
  toolChoice: sdkToolChoice,
11146
11258
  temperature: body.temperature,
11147
- maxOutputTokens: body.max_completion_tokens ?? body.max_tokens
11259
+ maxOutputTokens: body.max_completion_tokens ?? body.max_tokens,
11260
+ headers: requestHeaders
11148
11261
  };
11149
11262
  }
11150
11263
  function toOpenAiFinishReason(reason) {
@@ -11365,6 +11478,12 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog, suba
11365
11478
  const clientWantsStream = Boolean(body.stream);
11366
11479
  const forwardBody = { ...body, model: upstreamModelId(model) };
11367
11480
  const isOAuth = model.authType === "oauth";
11481
+ const upstreamHeaders = openCodeGoHeaders(
11482
+ model.providerId ?? model.sourceBackend,
11483
+ model.baseUrl ?? messagesUrl,
11484
+ extractConversationId(req.headers, body),
11485
+ model.headers
11486
+ ) ?? model.headers;
11368
11487
  let effectiveBeta = inboundBeta;
11369
11488
  let claudeCodeSessionId;
11370
11489
  if (isOAuth) {
@@ -11386,7 +11505,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog, suba
11386
11505
  isOAuth ? "oauth" : "api",
11387
11506
  (message) => plog(message),
11388
11507
  claudeCodeSessionId,
11389
- model.headers,
11508
+ upstreamHeaders,
11390
11509
  refreshToken,
11391
11510
  (refreshed) => {
11392
11511
  model.apiKey = refreshed;
@@ -11436,7 +11555,16 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog, suba
11436
11555
  interleavedReasoningField: model.interleavedReasoningField,
11437
11556
  upstreamModelId: upstreamModelId(model)
11438
11557
  },
11439
- maxTools: npmMaxTools
11558
+ maxTools: npmMaxTools,
11559
+ ...(() => {
11560
+ const requestHeaders = openCodeGoHeaders(
11561
+ model.providerId ?? model.sourceBackend,
11562
+ model.apiBaseUrl ?? model.baseUrl,
11563
+ extractConversationId(req.headers, body),
11564
+ model.headers
11565
+ );
11566
+ return requestHeaders ? { requestHeaders } : {};
11567
+ })()
11440
11568
  });
11441
11569
  const clientWantsStream = Boolean(body.stream);
11442
11570
  const responseModelId = getResponseModelId(body.model, model, options);
@@ -11513,8 +11641,25 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
11513
11641
  const completionsUrl = model.completionsUrl ? model.completionsUrl : `${backendFor(options, model).baseUrl}/v1/chat/completions`;
11514
11642
  const apiKey2 = model.apiKey ?? options.apiKey;
11515
11643
  const forwardBody = { ...body, model: upstreamModelId(model) };
11644
+ const upstreamHeaders = openCodeGoHeaders(
11645
+ model.providerId ?? model.sourceBackend,
11646
+ model.completionsUrl ?? model.apiBaseUrl,
11647
+ extractConversationId(req.headers, body),
11648
+ model.headers
11649
+ ) ?? model.headers;
11516
11650
  plog(() => `openai-direct-passthrough \u2192 ${completionsUrl} model=${forwardBody.model} stream=${Boolean(body.stream)}`);
11517
- await relayAnthropicMessages(res, completionsUrl, forwardBody, apiKey2, Boolean(body.stream), void 0, void 0, (message) => plog(message));
11651
+ await relayAnthropicMessages(
11652
+ res,
11653
+ completionsUrl,
11654
+ forwardBody,
11655
+ apiKey2,
11656
+ Boolean(body.stream),
11657
+ void 0,
11658
+ void 0,
11659
+ (message) => plog(message),
11660
+ void 0,
11661
+ upstreamHeaders
11662
+ );
11518
11663
  return;
11519
11664
  }
11520
11665
  const npm = model.npm || (model.modelFormat === "anthropic" ? "@ai-sdk/anthropic" : void 0);
@@ -11533,7 +11678,13 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
11533
11678
  options.vertex,
11534
11679
  providerRefreshToken(model.providerId, model.authType)
11535
11680
  );
11536
- const params = translateOpenAiRequest(body);
11681
+ const requestHeaders = openCodeGoHeaders(
11682
+ model.providerId ?? model.sourceBackend,
11683
+ baseURL,
11684
+ extractConversationId(req.headers, body),
11685
+ model.headers
11686
+ );
11687
+ const params = translateOpenAiRequest(body, requestHeaders);
11537
11688
  const clientWantsStream = Boolean(body.stream);
11538
11689
  const responseModelId = getResponseModelId(body.model, model, options);
11539
11690
  plog(() => `sdk-openai npm=${npm} upstream=${upstreamModelId(model)} responseModel=${responseModelId} stream=${clientWantsStream}`);
@@ -13835,6 +13986,9 @@ export {
13835
13986
  formatUpstreamErrorTrace,
13836
13987
  formatUpstreamError,
13837
13988
  upstreamHttpStatus,
13989
+ OPENCODE_SESSION_HEADER,
13990
+ extractConversationId,
13991
+ openCodeGoHeaders,
13838
13992
  aliasModelId,
13839
13993
  startProxyCatalog,
13840
13994
  startProxy,
@@ -13910,4 +14064,4 @@ export {
13910
14064
  supportsClaudeTransparentMode,
13911
14065
  buildHttpProxyRoutes
13912
14066
  };
13913
- //# sourceMappingURL=chunk-BJ4AE3PS.js.map
14067
+ //# sourceMappingURL=chunk-2NXLK3O6.js.map