@jacobbd/relay-ai 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.2",
14
+ version: "0.7.4",
15
15
  publishConfig: {
16
16
  access: "public"
17
17
  },
@@ -75,6 +75,7 @@ var package_default = {
75
75
  "@clack/prompts": "^0.9.1",
76
76
  "@openrouter/ai-sdk-provider": "^2.9.0",
77
77
  ai: "^6.0.197",
78
+ "cross-spawn": "^7.0.6",
78
79
  "gitlab-ai-provider": "^6.8.0",
79
80
  graphql: "^16.14.2",
80
81
  "ipaddr.js": "^2.4.0",
@@ -87,6 +88,7 @@ var package_default = {
87
88
  zod: "^3.25.76"
88
89
  },
89
90
  devDependencies: {
91
+ "@types/cross-spawn": "^6.0.6",
90
92
  "@types/node": "^22.0.0",
91
93
  "@types/node-forge": "^1.3.14",
92
94
  "@types/ws": "^8.18.1",
@@ -1900,9 +1902,28 @@ function setServerListenMode(listenMode) {
1900
1902
  };
1901
1903
  writeConfig(config);
1902
1904
  }
1905
+ function getServerAutostart() {
1906
+ return readConfig().server?.autostart ?? false;
1907
+ }
1908
+ function setServerAutostart(autostart) {
1909
+ const config = readConfig();
1910
+ config.server = {
1911
+ ...config.server ?? {},
1912
+ autostart
1913
+ };
1914
+ writeConfig(config);
1915
+ }
1916
+ function resolveServerAutostart(env = process.env) {
1917
+ const envVal = env["RELAY_AI_SERVER_AUTOSTART"]?.trim().toLowerCase();
1918
+ if (envVal !== void 0 && envVal !== "") {
1919
+ return ["1", "true", "yes", "on"].includes(envVal);
1920
+ }
1921
+ return getServerAutostart();
1922
+ }
1903
1923
 
1904
1924
  // src/launch.ts
1905
- import { execSync, spawn } from "child_process";
1925
+ import { execSync } from "child_process";
1926
+ import spawn from "cross-spawn";
1906
1927
  import { existsSync as existsSync3, appendFileSync } from "fs";
1907
1928
  import { homedir as homedir3 } from "os";
1908
1929
  import { join as join4 } from "path";
@@ -1993,8 +2014,7 @@ function launchClaude(env, model, extraArgs) {
1993
2014
  };
1994
2015
  const child = spawn(claudePath, args, {
1995
2016
  stdio: "inherit",
1996
- env,
1997
- shell: isWindows
2017
+ env
1998
2018
  });
1999
2019
  const forward = (signal) => {
2000
2020
  child.kill(signal);
@@ -4429,6 +4449,52 @@ function formatOpenAIModels(models) {
4429
4449
  };
4430
4450
  }
4431
4451
 
4452
+ // src/anthropic-endpoints.ts
4453
+ var MESSAGE_PATH = "/v1/messages";
4454
+ var COUNT_TOKENS_PATH = "/v1/messages/count_tokens";
4455
+ var MODELS_PATH = "/v1/models";
4456
+ function anthropicModelsEndpoint(url) {
4457
+ if (!url) return null;
4458
+ try {
4459
+ const pathname = new URL(url, "http://relay.local").pathname;
4460
+ if (pathname === MODELS_PATH || pathname === `${MODELS_PATH}/`) return "list";
4461
+ if (pathname.startsWith(`${MODELS_PATH}/`)) {
4462
+ const id = decodeURIComponent(pathname.slice(MODELS_PATH.length + 1));
4463
+ if (id) return { id };
4464
+ }
4465
+ } catch {
4466
+ }
4467
+ return null;
4468
+ }
4469
+ function anthropicMessagesEndpoint(url) {
4470
+ if (!url) return null;
4471
+ try {
4472
+ const pathname = new URL(url, "http://relay.local").pathname;
4473
+ if (pathname === MESSAGE_PATH) return "messages";
4474
+ if (pathname === COUNT_TOKENS_PATH) return "count_tokens";
4475
+ } catch {
4476
+ }
4477
+ return null;
4478
+ }
4479
+ var NON_CONTEXT_FIELDS = /* @__PURE__ */ new Set([
4480
+ "model",
4481
+ "stream",
4482
+ "max_tokens",
4483
+ "temperature",
4484
+ "top_p",
4485
+ "top_k",
4486
+ "stop_sequences",
4487
+ "metadata"
4488
+ ]);
4489
+ function estimateAnthropicInputTokens(body) {
4490
+ const contextBody = Object.fromEntries(
4491
+ Object.entries(body).filter(([key]) => !NON_CONTEXT_FIELDS.has(key))
4492
+ );
4493
+ const serialized = JSON.stringify(contextBody);
4494
+ if (!serialized || serialized === "{}") return 0;
4495
+ return Math.max(1, Math.ceil(Buffer.byteLength(serialized, "utf8") / 4));
4496
+ }
4497
+
4432
4498
  // src/upstream-forward.ts
4433
4499
  import { Readable } from "stream";
4434
4500
 
@@ -4646,7 +4712,9 @@ function encodeToolUseId(rawId, thoughtSignature, inline = true) {
4646
4712
  return `${rawId}${TOOL_USE_SIG_SEP}${encoded}`;
4647
4713
  }
4648
4714
  function serializeToolResultContent(content) {
4649
- return typeof content === "string" ? content : JSON.stringify(content);
4715
+ if (typeof content === "string") return content;
4716
+ if (content === void 0) return "";
4717
+ return JSON.stringify(content);
4650
4718
  }
4651
4719
 
4652
4720
  // src/antigravity/request-adapter.ts
@@ -5432,6 +5500,8 @@ function sanitizeMessage(message) {
5432
5500
  }
5433
5501
 
5434
5502
  // src/sdk-adapter.ts
5503
+ var NEEDS_TRAILING_TOOL_NUDGE = /* @__PURE__ */ new Set(["@ai-sdk/alibaba"]);
5504
+ var TRAILING_TOOL_NUDGE_TEXT = "Continue.";
5435
5505
  function anthropicEffortFromRequest(body) {
5436
5506
  const effort = body.output_config?.effort;
5437
5507
  if (typeof effort === "string" && effort.trim()) return effort.trim();
@@ -5492,7 +5562,7 @@ function thinkingToSdkPart(block, npm) {
5492
5562
  }
5493
5563
  return part;
5494
5564
  }
5495
- function translateMessages(messages, npm) {
5565
+ function translateMessages(messages, npm, onDebug) {
5496
5566
  const isGoogle = npm === "@ai-sdk/google";
5497
5567
  const out = [];
5498
5568
  for (const msg of messages) {
@@ -5507,6 +5577,10 @@ function translateMessages(messages, npm) {
5507
5577
  if (p8) parts.push(p8);
5508
5578
  }
5509
5579
  }
5580
+ if (blocks.length && !toolResults.length && !parts.length) {
5581
+ const types = blocks.map((b) => b.type).join(", ");
5582
+ onDebug?.(`sdk: dropped user turn with unrecognized block types: [${types}]`);
5583
+ }
5510
5584
  if (toolResults.length) {
5511
5585
  out.push({
5512
5586
  role: "tool",
@@ -5542,6 +5616,10 @@ function translateMessages(messages, npm) {
5542
5616
  if (parts.length) out.push({ role: "assistant", content: parts });
5543
5617
  }
5544
5618
  }
5619
+ if (NEEDS_TRAILING_TOOL_NUDGE.has(npm) && out[out.length - 1]?.role === "tool") {
5620
+ out.push({ role: "user", content: [{ type: "text", text: TRAILING_TOOL_NUDGE_TEXT }] });
5621
+ onDebug?.("sdk: appended continuation nudge (request ended on tool result)");
5622
+ }
5545
5623
  return out;
5546
5624
  }
5547
5625
  function stripNullInputs(input) {
@@ -5592,7 +5670,7 @@ function translateRequest2(body, npm, options) {
5592
5670
  }
5593
5671
  return {
5594
5672
  system: options?.openAiOAuth ? void 0 : systemText,
5595
- messages: translateMessages(messages, npm),
5673
+ messages: translateMessages(messages, npm, options?.onDebug),
5596
5674
  tools: translateTools3(upstreamTools.length ? upstreamTools : void 0),
5597
5675
  toolChoice: translateToolChoice(body.tool_choice),
5598
5676
  maxOutputTokens: options?.openAiOAuth ? void 0 : body.max_tokens,
@@ -5600,7 +5678,7 @@ function translateRequest2(body, npm, options) {
5600
5678
  providerOptions
5601
5679
  };
5602
5680
  }
5603
- async function writeAnthropicStream(fullStream, modelId, write, log7) {
5681
+ async function writeAnthropicStream(fullStream, modelId, write, log7, estimatedInputTokens = 0) {
5604
5682
  const messageId = "msg_" + Date.now();
5605
5683
  let blockIndex = -1;
5606
5684
  let started = false;
@@ -5608,7 +5686,7 @@ async function writeAnthropicStream(fullStream, modelId, write, log7) {
5608
5686
  let pendingThinkingSig;
5609
5687
  const idToBlock = /* @__PURE__ */ new Map();
5610
5688
  let finishReason = "end_turn";
5611
- let usage = { input_tokens: 0, output_tokens: 0 };
5689
+ let usage = { input_tokens: estimatedInputTokens, output_tokens: 0 };
5612
5690
  const emit = (event, data) => write(sseChunk(event, data));
5613
5691
  const ensureStart = () => {
5614
5692
  if (started) return;
@@ -5622,7 +5700,7 @@ async function writeAnthropicStream(fullStream, modelId, write, log7) {
5622
5700
  model: modelId,
5623
5701
  stop_reason: null,
5624
5702
  stop_sequence: null,
5625
- usage: { input_tokens: 0, output_tokens: 0 }
5703
+ usage: { input_tokens: estimatedInputTokens, output_tokens: 0 }
5626
5704
  }
5627
5705
  });
5628
5706
  started = true;
@@ -5721,7 +5799,7 @@ async function writeAnthropicStream(fullStream, modelId, write, log7) {
5721
5799
  case "finish":
5722
5800
  if (part.totalUsage) {
5723
5801
  usage = {
5724
- input_tokens: part.totalUsage.inputTokens ?? 0,
5802
+ input_tokens: part.totalUsage.inputTokens ?? estimatedInputTokens,
5725
5803
  output_tokens: part.totalUsage.outputTokens ?? 0
5726
5804
  };
5727
5805
  }
@@ -5747,7 +5825,7 @@ async function writeAnthropicStream(fullStream, modelId, write, log7) {
5747
5825
  emit("message_delta", { type: "message_delta", delta: { stop_reason: finishReason, stop_sequence: null }, usage });
5748
5826
  emit("message_stop", { type: "message_stop" });
5749
5827
  }
5750
- async function streamAnthropicResponse(model, params, modelId, write, log7) {
5828
+ async function streamAnthropicResponse(model, params, modelId, write, log7, estimatedInputTokens = 0) {
5751
5829
  const result = streamText({ model, ...params, onError: () => {
5752
5830
  } });
5753
5831
  Promise.resolve(result.text).catch(() => {
@@ -5760,7 +5838,13 @@ async function streamAnthropicResponse(model, params, modelId, write, log7) {
5760
5838
  });
5761
5839
  Promise.resolve(result.usage).catch(() => {
5762
5840
  });
5763
- await writeAnthropicStream(result.fullStream, modelId, write, log7);
5841
+ await writeAnthropicStream(
5842
+ result.fullStream,
5843
+ modelId,
5844
+ write,
5845
+ log7,
5846
+ estimatedInputTokens
5847
+ );
5764
5848
  }
5765
5849
  async function generateAnthropicResponse(model, params, modelId, options) {
5766
5850
  let text4;
@@ -5965,6 +6049,7 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
5965
6049
  const params = translateRequest2(anthropicBody, route.npm, {
5966
6050
  openAiOAuth,
5967
6051
  maxTools: maxToolsForNpm(route.npm),
6052
+ onDebug: (msg) => plog(() => msg),
5968
6053
  reasoningMetadata: {
5969
6054
  providerId: route.providerId,
5970
6055
  apiBaseUrl: route.baseURL,
@@ -5998,7 +6083,14 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
5998
6083
  "Cache-Control": "no-cache",
5999
6084
  "Connection": "keep-alive"
6000
6085
  });
6001
- await streamAnthropicResponse(model, params, originalModel, (c) => res.write(c), plog);
6086
+ await streamAnthropicResponse(
6087
+ model,
6088
+ params,
6089
+ originalModel,
6090
+ (c) => res.write(c),
6091
+ plog,
6092
+ estimateAnthropicInputTokens(anthropicBody)
6093
+ );
6002
6094
  res.end();
6003
6095
  } else {
6004
6096
  const anthropicResponse = await generateAnthropicResponse(
@@ -9870,6 +9962,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
9870
9962
  const params = translateRequest2(body, model.npm, {
9871
9963
  defaultEffort: anthropicEffortFromRequest(body) ? void 0 : model.defaultEffort,
9872
9964
  openAiOAuth: model.npm === "@ai-sdk/openai" && model.authType === "oauth",
9965
+ onDebug: plog,
9873
9966
  reasoningMetadata: {
9874
9967
  providerId: model.providerId,
9875
9968
  apiBaseUrl: model.apiBaseUrl,
@@ -9890,7 +9983,14 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
9890
9983
  "Cache-Control": "no-cache",
9891
9984
  "Connection": "keep-alive"
9892
9985
  });
9893
- await streamAnthropicResponse(languageModel, params, responseModelId, (chunk) => res.write(chunk));
9986
+ await streamAnthropicResponse(
9987
+ languageModel,
9988
+ params,
9989
+ responseModelId,
9990
+ (chunk) => res.write(chunk),
9991
+ void 0,
9992
+ estimateAnthropicInputTokens(body)
9993
+ );
9894
9994
  res.end();
9895
9995
  } else {
9896
9996
  const anthropicResponse = await generateAnthropicResponse(languageModel, params, responseModelId);
@@ -11818,6 +11918,8 @@ export {
11818
11918
  setServerFreeModelsOnly,
11819
11919
  getServerListenMode,
11820
11920
  setServerListenMode,
11921
+ setServerAutostart,
11922
+ resolveServerAutostart,
11821
11923
  findBinaryOnPath,
11822
11924
  findClaudeBinary,
11823
11925
  launchClaude,
@@ -11891,6 +11993,9 @@ export {
11891
11993
  openAiIdCollisions,
11892
11994
  createGatewayModelCatalog,
11893
11995
  buildDedupedModelRows,
11996
+ anthropicModelsEndpoint,
11997
+ anthropicMessagesEndpoint,
11998
+ estimateAnthropicInputTokens,
11894
11999
  grabRoundTripSignature,
11895
12000
  silenceSdkWarnings,
11896
12001
  parseToolArguments,
@@ -11966,4 +12071,4 @@ export {
11966
12071
  supportsClaudeTransparentMode,
11967
12072
  buildHttpProxyRoutes
11968
12073
  };
11969
- //# sourceMappingURL=chunk-LNQ46VW7.js.map
12074
+ //# sourceMappingURL=chunk-EUY2MVOS.js.map