opencode-claude-max-proxy 1.12.1 → 1.12.2

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.
@@ -1,3 +1,4 @@
1
+ import { createRequire } from "node:module";
1
2
  var __defProp = Object.defineProperty;
2
3
  var __returnValue = (v) => v;
3
4
  function __exportSetter(name, newValue) {
@@ -12,6 +13,7 @@ var __export = (target, all) => {
12
13
  set: __exportSetter.bind(all, name)
13
14
  });
14
15
  };
16
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
15
17
 
16
18
  // node_modules/hono/dist/compose.js
17
19
  var compose = (middleware, onError, onNotFound) => {
@@ -2239,10 +2241,11 @@ var claudeLog = (event, extra) => {
2239
2241
  };
2240
2242
 
2241
2243
  // src/proxy/server.ts
2242
- import { execSync } from "child_process";
2244
+ import { exec as execCallback } from "child_process";
2243
2245
  import { existsSync as existsSync2 } from "fs";
2244
2246
  import { fileURLToPath as fileURLToPath3 } from "url";
2245
- import { join as join2, dirname as dirname3 } from "path";
2247
+ import { join as join2, dirname as dirname2 } from "path";
2248
+ import { promisify as promisify2 } from "util";
2246
2249
 
2247
2250
  // src/mcpTools.ts
2248
2251
  import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
@@ -12071,10 +12074,53 @@ function stripMcpPrefix(toolName) {
12071
12074
  }
12072
12075
 
12073
12076
  // src/proxy/sessionStore.ts
12074
- import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from "fs";
12075
- import { join } from "path";
12076
- import { homedir } from "os";
12077
+ import {
12078
+ closeSync,
12079
+ existsSync,
12080
+ mkdirSync,
12081
+ openSync,
12082
+ readFileSync,
12083
+ renameSync,
12084
+ statSync,
12085
+ unlinkSync,
12086
+ writeFileSync
12087
+ } from "node:fs";
12088
+ import { homedir } from "node:os";
12089
+ import { join } from "node:path";
12077
12090
  var SESSION_TTL_MS = 24 * 60 * 60 * 1000;
12091
+ var STALE_LOCK_THRESHOLD_MS = 30000;
12092
+ function acquireLock(lockPath) {
12093
+ try {
12094
+ const fd = openSync(lockPath, "wx");
12095
+ closeSync(fd);
12096
+ return true;
12097
+ } catch (e) {
12098
+ const err = e;
12099
+ if (err.code !== "EEXIST") {
12100
+ console.error("[sessionStore] lock acquire failed:", err.message);
12101
+ return false;
12102
+ }
12103
+ try {
12104
+ const stat = statSync(lockPath);
12105
+ if (Date.now() - stat.mtimeMs > STALE_LOCK_THRESHOLD_MS) {
12106
+ unlinkSync(lockPath);
12107
+ const fd = openSync(lockPath, "wx");
12108
+ closeSync(fd);
12109
+ return true;
12110
+ }
12111
+ } catch (staleError) {
12112
+ console.error("[sessionStore] stale lock recovery failed:", staleError.message);
12113
+ }
12114
+ return false;
12115
+ }
12116
+ }
12117
+ function releaseLock(lockPath) {
12118
+ try {
12119
+ unlinkSync(lockPath);
12120
+ } catch (e) {
12121
+ console.error("[sessionStore] lock release failed:", e.message);
12122
+ }
12123
+ }
12078
12124
  function getStorePath() {
12079
12125
  const dir = process.env.CLAUDE_PROXY_SESSION_DIR || join(homedir(), ".cache", "opencode-claude-max-proxy");
12080
12126
  if (!existsSync(dir)) {
@@ -12097,20 +12143,24 @@ function readStore() {
12097
12143
  }
12098
12144
  }
12099
12145
  return pruned;
12100
- } catch {
12146
+ } catch (e) {
12147
+ console.error("[sessionStore] read failed:", e.message);
12101
12148
  return {};
12102
12149
  }
12103
12150
  }
12104
12151
  function writeStore(store) {
12105
12152
  const path3 = getStorePath();
12106
- const tmp = path3 + ".tmp";
12153
+ const tmp = `${path3}.tmp`;
12107
12154
  try {
12108
12155
  writeFileSync(tmp, JSON.stringify(store, null, 2));
12109
12156
  renameSync(tmp, path3);
12110
- } catch {
12157
+ } catch (e) {
12158
+ console.error("[sessionStore] write failed:", e.message);
12111
12159
  try {
12112
12160
  writeFileSync(path3, JSON.stringify(store, null, 2));
12113
- } catch {}
12161
+ } catch (directWriteError) {
12162
+ console.error("[sessionStore] write failed:", directWriteError.message);
12163
+ }
12114
12164
  }
12115
12165
  }
12116
12166
  function lookupSharedSession(key) {
@@ -12123,29 +12173,152 @@ function lookupSharedSession(key) {
12123
12173
  return session;
12124
12174
  }
12125
12175
  function storeSharedSession(key, claudeSessionId, messageCount) {
12126
- const store = readStore();
12127
- const existing = store[key];
12128
- store[key] = {
12129
- claudeSessionId,
12130
- createdAt: existing?.createdAt || Date.now(),
12131
- lastUsedAt: Date.now(),
12132
- messageCount: messageCount ?? existing?.messageCount ?? 0
12133
- };
12134
- writeStore(store);
12176
+ const path3 = getStorePath();
12177
+ const lockPath = `${path3}.lock`;
12178
+ const hasLock = acquireLock(lockPath);
12179
+ if (!hasLock) {
12180
+ console.warn("[sessionStore] could not acquire lock, proceeding without");
12181
+ }
12182
+ try {
12183
+ const store = readStore();
12184
+ const existing = store[key];
12185
+ store[key] = {
12186
+ claudeSessionId,
12187
+ createdAt: existing?.createdAt || Date.now(),
12188
+ lastUsedAt: Date.now(),
12189
+ messageCount: messageCount ?? existing?.messageCount ?? 0
12190
+ };
12191
+ writeStore(store);
12192
+ } finally {
12193
+ if (hasLock) {
12194
+ releaseLock(lockPath);
12195
+ }
12196
+ }
12135
12197
  }
12136
12198
  function clearSharedSessions() {
12137
12199
  const path3 = getStorePath();
12138
12200
  try {
12139
12201
  writeFileSync(path3, "{}");
12140
- } catch {}
12202
+ } catch (e) {
12203
+ console.error("[sessionStore] clear failed:", e.message);
12204
+ }
12205
+ }
12206
+
12207
+ // src/utils/lruMap.ts
12208
+ class LRUMap {
12209
+ maxSize;
12210
+ onEvict;
12211
+ map = new Map;
12212
+ constructor(maxSize, onEvict) {
12213
+ this.maxSize = maxSize;
12214
+ this.onEvict = onEvict;
12215
+ }
12216
+ get size() {
12217
+ return this.map.size;
12218
+ }
12219
+ get(key) {
12220
+ const value = this.map.get(key);
12221
+ if (value === undefined)
12222
+ return;
12223
+ this.map.delete(key);
12224
+ this.map.set(key, value);
12225
+ return value;
12226
+ }
12227
+ set(key, value) {
12228
+ if (this.map.has(key)) {
12229
+ this.map.delete(key);
12230
+ } else if (this.map.size >= this.maxSize) {
12231
+ this.evictOldest();
12232
+ }
12233
+ this.map.set(key, value);
12234
+ return this;
12235
+ }
12236
+ has(key) {
12237
+ return this.map.has(key);
12238
+ }
12239
+ delete(key) {
12240
+ return this.map.delete(key);
12241
+ }
12242
+ clear() {
12243
+ this.map.clear();
12244
+ }
12245
+ entries() {
12246
+ return this.map.entries();
12247
+ }
12248
+ keys() {
12249
+ return this.map.keys();
12250
+ }
12251
+ values() {
12252
+ return this.map.values();
12253
+ }
12254
+ forEach(callbackfn) {
12255
+ this.map.forEach((value, key) => callbackfn(value, key, this));
12256
+ }
12257
+ [Symbol.iterator]() {
12258
+ return this.map[Symbol.iterator]();
12259
+ }
12260
+ evictOldest() {
12261
+ const oldestKey = this.map.keys().next().value;
12262
+ if (oldestKey === undefined)
12263
+ return;
12264
+ const oldestValue = this.map.get(oldestKey);
12265
+ if (oldestValue === undefined)
12266
+ return;
12267
+ this.map.delete(oldestKey);
12268
+ this.onEvict?.(oldestKey, oldestValue);
12269
+ }
12141
12270
  }
12142
12271
 
12143
12272
  // src/proxy/server.ts
12144
- var sessionCache = new Map;
12145
- var fingerprintCache = new Map;
12273
+ var DEFAULT_MAX_SESSIONS = 1000;
12274
+ function getMaxSessionsLimit() {
12275
+ const raw2 = process.env.CLAUDE_PROXY_MAX_SESSIONS;
12276
+ if (!raw2)
12277
+ return DEFAULT_MAX_SESSIONS;
12278
+ const parsed = Number.parseInt(raw2, 10);
12279
+ if (!Number.isFinite(parsed) || parsed <= 0) {
12280
+ console.warn(`[PROXY] Invalid CLAUDE_PROXY_MAX_SESSIONS value "${raw2}"; using default ${DEFAULT_MAX_SESSIONS}`);
12281
+ return DEFAULT_MAX_SESSIONS;
12282
+ }
12283
+ return parsed;
12284
+ }
12285
+ function removeFingerprintEntriesByClaudeSessionId(claudeSessionId) {
12286
+ for (const [key, state] of fingerprintCache.entries()) {
12287
+ if (state.claudeSessionId === claudeSessionId) {
12288
+ fingerprintCache.delete(key);
12289
+ }
12290
+ }
12291
+ }
12292
+ function removeSessionEntriesByClaudeSessionId(claudeSessionId) {
12293
+ for (const [key, state] of sessionCache.entries()) {
12294
+ if (state.claudeSessionId === claudeSessionId) {
12295
+ sessionCache.delete(key);
12296
+ }
12297
+ }
12298
+ }
12299
+ function createSessionCache(maxSize) {
12300
+ return new LRUMap(maxSize, (_key, evictedState) => {
12301
+ removeFingerprintEntriesByClaudeSessionId(evictedState.claudeSessionId);
12302
+ });
12303
+ }
12304
+ function createFingerprintCache(maxSize) {
12305
+ return new LRUMap(maxSize, (_key, evictedState) => {
12306
+ removeSessionEntriesByClaudeSessionId(evictedState.claudeSessionId);
12307
+ });
12308
+ }
12309
+ var activeMaxSessions = getMaxSessionsLimit();
12310
+ var sessionCache = createSessionCache(activeMaxSessions);
12311
+ var fingerprintCache = createFingerprintCache(activeMaxSessions);
12146
12312
  function clearSessionCache() {
12147
- sessionCache.clear();
12148
- fingerprintCache.clear();
12313
+ const configuredLimit = getMaxSessionsLimit();
12314
+ if (configuredLimit !== activeMaxSessions) {
12315
+ activeMaxSessions = configuredLimit;
12316
+ sessionCache = createSessionCache(activeMaxSessions);
12317
+ fingerprintCache = createFingerprintCache(activeMaxSessions);
12318
+ } else {
12319
+ sessionCache.clear();
12320
+ fingerprintCache.clear();
12321
+ }
12149
12322
  try {
12150
12323
  clearSharedSessions();
12151
12324
  } catch {}
@@ -12332,21 +12505,40 @@ var ALLOWED_MCP_TOOLS = [
12332
12505
  `mcp__${MCP_SERVER_NAME}__glob`,
12333
12506
  `mcp__${MCP_SERVER_NAME}__grep`
12334
12507
  ];
12335
- function resolveClaudeExecutable() {
12336
- try {
12337
- const sdkPath = fileURLToPath3(import.meta.resolve("@anthropic-ai/claude-agent-sdk"));
12338
- const sdkCliJs = join2(dirname3(sdkPath), "cli.js");
12339
- if (existsSync2(sdkCliJs))
12340
- return sdkCliJs;
12341
- } catch {}
12508
+ var exec2 = promisify2(execCallback);
12509
+ var cachedClaudePath = null;
12510
+ var cachedClaudePathPromise = null;
12511
+ var claudeExecutable = "";
12512
+ async function resolveClaudeExecutableAsync() {
12513
+ if (cachedClaudePath)
12514
+ return cachedClaudePath;
12515
+ if (cachedClaudePathPromise)
12516
+ return cachedClaudePathPromise;
12517
+ cachedClaudePathPromise = (async () => {
12518
+ try {
12519
+ const sdkPath = fileURLToPath3(import.meta.resolve("@anthropic-ai/claude-agent-sdk"));
12520
+ const sdkCliJs = join2(dirname2(sdkPath), "cli.js");
12521
+ if (existsSync2(sdkCliJs)) {
12522
+ cachedClaudePath = sdkCliJs;
12523
+ return sdkCliJs;
12524
+ }
12525
+ } catch {}
12526
+ try {
12527
+ const { stdout } = await exec2("which claude");
12528
+ const claudePath = stdout.trim();
12529
+ if (claudePath && existsSync2(claudePath)) {
12530
+ cachedClaudePath = claudePath;
12531
+ return claudePath;
12532
+ }
12533
+ } catch {}
12534
+ throw new Error("Could not find Claude Code executable. Install via: npm install -g @anthropic-ai/claude-code");
12535
+ })();
12342
12536
  try {
12343
- const claudePath = execSync("which claude", { encoding: "utf-8" }).trim();
12344
- if (claudePath && existsSync2(claudePath))
12345
- return claudePath;
12346
- } catch {}
12347
- throw new Error("Could not find Claude Code executable. Install via: npm install -g @anthropic-ai/claude-code");
12537
+ return await cachedClaudePathPromise;
12538
+ } finally {
12539
+ cachedClaudePathPromise = null;
12540
+ }
12348
12541
  }
12349
- var claudeExecutable = resolveClaudeExecutable();
12350
12542
  function mapModelToClaudeModel(model) {
12351
12543
  if (model.includes("opus"))
12352
12544
  return "opus";
@@ -12482,13 +12674,6 @@ IMPORTANT: When using the task/Task tool, the subagent_type parameter must be on
12482
12674
  }
12483
12675
  }
12484
12676
  } else {
12485
- if (systemContext) {
12486
- structured.push({
12487
- type: "user",
12488
- message: { role: "user", content: systemContext },
12489
- parent_tool_use_id: null
12490
- });
12491
- }
12492
12677
  for (const m of messagesToConvert) {
12493
12678
  if (m.role === "user") {
12494
12679
  structured.push({
@@ -12556,9 +12741,7 @@ IMPORTANT: When using the task/Task tool, the subagent_type parameter must be on
12556
12741
  }).join(`
12557
12742
 
12558
12743
  `) || "";
12559
- prompt = !isResume && systemContext ? `${systemContext}
12560
-
12561
- ${conversationParts}` : conversationParts;
12744
+ prompt = conversationParts;
12562
12745
  }
12563
12746
  const passthrough = Boolean(process.env.CLAUDE_PROXY_PASSTHROUGH);
12564
12747
  const capturedToolUses = [];
@@ -12603,6 +12786,9 @@ ${conversationParts}` : conversationParts;
12603
12786
  let currentSessionId;
12604
12787
  claudeLog("upstream.start", { mode: "non_stream", model });
12605
12788
  try {
12789
+ if (!claudeExecutable) {
12790
+ claudeExecutable = await resolveClaudeExecutableAsync();
12791
+ }
12606
12792
  const response = query({
12607
12793
  prompt,
12608
12794
  options: {
@@ -12612,6 +12798,9 @@ ${conversationParts}` : conversationParts;
12612
12798
  pathToClaudeCodeExecutable: claudeExecutable,
12613
12799
  permissionMode: "bypassPermissions",
12614
12800
  allowDangerouslySkipPermissions: true,
12801
+ ...systemContext ? {
12802
+ systemPrompt: { type: "preset", preset: "claude_code", append: systemContext }
12803
+ } : {},
12615
12804
  ...passthrough ? {
12616
12805
  disallowedTools: [...BLOCKED_BUILTIN_TOOLS, ...CLAUDE_CODE_ONLY_TOOLS],
12617
12806
  ...passthroughMcp ? {
@@ -12759,6 +12948,9 @@ ${conversationParts}` : conversationParts;
12759
12948
  includePartialMessages: true,
12760
12949
  permissionMode: "bypassPermissions",
12761
12950
  allowDangerouslySkipPermissions: true,
12951
+ ...systemContext ? {
12952
+ systemPrompt: { type: "preset", preset: "claude_code", append: systemContext }
12953
+ } : {},
12762
12954
  ...passthrough ? {
12763
12955
  disallowedTools: [...BLOCKED_BUILTIN_TOOLS, ...CLAUDE_CODE_ONLY_TOOLS],
12764
12956
  ...passthroughMcp ? {
@@ -13032,10 +13224,10 @@ data: ${JSON.stringify({
13032
13224
  };
13033
13225
  app.post("/v1/messages", (c) => handleWithQueue(c, "/v1/messages"));
13034
13226
  app.post("/messages", (c) => handleWithQueue(c, "/messages"));
13035
- app.get("/health", (c) => {
13227
+ app.get("/health", async (c) => {
13036
13228
  try {
13037
- const authJson = execSync("claude auth status", { encoding: "utf-8", timeout: 5000 });
13038
- const auth = JSON.parse(authJson);
13229
+ const { stdout } = await exec2("claude auth status", { timeout: 5000 });
13230
+ const auth = JSON.parse(stdout);
13039
13231
  if (!auth.loggedIn) {
13040
13232
  return c.json({
13041
13233
  status: "unhealthy",
@@ -13067,6 +13259,7 @@ data: ${JSON.stringify({
13067
13259
  return { app, config: finalConfig };
13068
13260
  }
13069
13261
  async function startProxyServer(config = {}) {
13262
+ claudeExecutable = await resolveClaudeExecutableAsync();
13070
13263
  const { app, config: finalConfig } = createProxyServer(config);
13071
13264
  const server = serve({
13072
13265
  fetch: app.fetch,
@@ -13099,4 +13292,4 @@ Or use a different port:`);
13099
13292
  return server;
13100
13293
  }
13101
13294
 
13102
- export { clearSessionCache, createProxyServer, startProxyServer };
13295
+ export { __require, getMaxSessionsLimit, clearSessionCache, createProxyServer, startProxyServer };
package/dist/cli.js CHANGED
@@ -1,10 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ __require,
3
4
  startProxyServer
4
- } from "./cli-s4sk7eks.js";
5
+ } from "./cli-m99nmtqk.js";
5
6
 
6
7
  // bin/cli.ts
7
- import { execSync } from "child_process";
8
+ import { exec as execCallback } from "child_process";
9
+ import { promisify } from "util";
10
+ var exec = promisify(execCallback);
8
11
  process.on("uncaughtException", (err) => {
9
12
  console.error(`[PROXY] Uncaught exception (recovered): ${err.message}`);
10
13
  });
@@ -14,17 +17,25 @@ process.on("unhandledRejection", (reason) => {
14
17
  var port = parseInt(process.env.CLAUDE_PROXY_PORT || "3456", 10);
15
18
  var host = process.env.CLAUDE_PROXY_HOST || "127.0.0.1";
16
19
  var idleTimeoutSeconds = parseInt(process.env.CLAUDE_PROXY_IDLE_TIMEOUT_SECONDS || "120", 10);
17
- try {
18
- const authJson = execSync("claude auth status", { encoding: "utf-8", timeout: 5000 });
19
- const auth = JSON.parse(authJson);
20
- if (!auth.loggedIn) {
21
- console.error("\x1B[31m✗ Not logged in to Claude.\x1B[0m Run: claude login");
22
- process.exit(1);
20
+ async function runCli(start = startProxyServer, runExec = exec) {
21
+ try {
22
+ const { stdout } = await runExec("claude auth status", { timeout: 5000 });
23
+ const auth = JSON.parse(stdout);
24
+ if (!auth.loggedIn) {
25
+ console.error("\x1B[31m✗ Not logged in to Claude.\x1B[0m Run: claude login");
26
+ process.exit(1);
27
+ }
28
+ if (auth.subscriptionType !== "max") {
29
+ console.error(`\x1B[33m⚠ Claude subscription: ${auth.subscriptionType || "unknown"} (Max recommended)\x1B[0m`);
30
+ }
31
+ } catch {
32
+ console.error("\x1B[33m⚠ Could not verify Claude auth status. If requests fail, run: claude login\x1B[0m");
23
33
  }
24
- if (auth.subscriptionType !== "max") {
25
- console.error(`\x1B[33m⚠ Claude subscription: ${auth.subscriptionType || "unknown"} (Max recommended)\x1B[0m`);
26
- }
27
- } catch {
28
- console.error("\x1B[33m⚠ Could not verify Claude auth status. If requests fail, run: claude login\x1B[0m");
34
+ await start({ port, host, idleTimeoutSeconds });
35
+ }
36
+ if (__require.main == __require.module) {
37
+ await runCli();
29
38
  }
30
- await startProxyServer({ port, host, idleTimeoutSeconds });
39
+ export {
40
+ runCli
41
+ };
package/dist/server.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import {
2
2
  clearSessionCache,
3
3
  createProxyServer,
4
+ getMaxSessionsLimit,
4
5
  startProxyServer
5
- } from "./cli-s4sk7eks.js";
6
+ } from "./cli-m99nmtqk.js";
6
7
  export {
7
8
  startProxyServer,
9
+ getMaxSessionsLimit,
8
10
  createProxyServer,
9
11
  clearSessionCache
10
12
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-claude-max-proxy",
3
- "version": "1.12.1",
3
+ "version": "1.12.2",
4
4
  "description": "Use your Claude Max subscription with OpenCode via proxy server",
5
5
  "type": "module",
6
6
  "main": "./dist/server.js",