@rynfar/meridian 1.25.1 → 1.26.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.
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  ---
13
13
 
14
- Meridian turns your Claude Max subscription into a local Anthropic API. Any tool that speaks the Anthropic protocol — OpenCode, Crush, Cline, Aider — connects to Meridian and gets Claude, powered by your existing subscription through the official Claude Code SDK.
14
+ Meridian turns your Claude Max subscription into a local Anthropic API. Any tool that speaks the Anthropic or OpenAI protocol — OpenCode, Crush, Cline, Aider, Open WebUI — connects to Meridian and gets Claude, powered by your existing subscription through the official Claude Code SDK.
15
15
 
16
16
  > [!NOTE]
17
17
  > **Renamed from `opencode-claude-max-proxy`.** If you're upgrading, see [`MIGRATION.md`](MIGRATION.md) for the checklist. Your existing sessions, env vars, and agent configs all continue to work.
@@ -53,6 +53,7 @@ Meridian bridges that gap. It runs locally, accepts standard Anthropic API reque
53
53
  ## Features
54
54
 
55
55
  - **Standard Anthropic API** — drop-in compatible with any tool that supports a custom `base_url`
56
+ - **OpenAI-compatible API** — `/v1/chat/completions` and `/v1/models` for tools that only speak the OpenAI protocol (Open WebUI, Continue, etc.) — no LiteLLM needed
56
57
  - **Session management** — conversations persist across requests, survive compaction and undo, resume after proxy restarts
57
58
  - **Streaming** — full SSE streaming with MCP tool filtering
58
59
  - **Concurrent sessions** — run parent and subagent requests in parallel
@@ -173,6 +174,24 @@ ANTHROPIC_API_KEY=x ANTHROPIC_BASE_URL=http://127.0.0.1:3456 \
173
174
 
174
175
  > **Note:** `--no-stream` is incompatible due to a litellm parsing issue — use the default streaming mode.
175
176
 
177
+ ### OpenAI-compatible tools (Open WebUI, Continue, etc.)
178
+
179
+ Meridian speaks the OpenAI protocol natively — no LiteLLM or translation proxy needed.
180
+
181
+ **`POST /v1/chat/completions`** — accepts OpenAI chat format, returns OpenAI completion format (streaming and non-streaming)
182
+
183
+ **`GET /v1/models`** — returns available Claude models in OpenAI format
184
+
185
+ Point any OpenAI-compatible tool at `http://127.0.0.1:3456` with any API key value:
186
+
187
+ ```bash
188
+ # Open WebUI: set OpenAI API base to http://127.0.0.1:3456, API key to any value
189
+ # Continue: set apiBase to http://127.0.0.1:3456 with provider: openai
190
+ # Any OpenAI SDK: set base_url="http://127.0.0.1:3456", api_key="dummy"
191
+ ```
192
+
193
+ > **Note:** Multi-turn conversations work by packing prior turns into the system prompt. Each request is a fresh SDK session — OpenAI clients replay full history themselves and don't use Meridian's session resumption.
194
+
176
195
  ### Any Anthropic-compatible tool
177
196
 
178
197
  ```bash
@@ -189,7 +208,8 @@ export ANTHROPIC_BASE_URL=http://127.0.0.1:3456
189
208
  | [Crush](https://github.com/charmbracelet/crush) | ✅ Verified | Provider config (see above) — full tool support, session resume, headless `crush run` |
190
209
  | [Cline](https://github.com/cline/cline) | ✅ Verified | Config (see above) — full tool support, file read/write/edit, bash, session resume |
191
210
  | [Aider](https://github.com/paul-gauthier/aider) | ✅ Verified | Env vars — file editing, streaming; `--no-stream` broken (litellm bug) |
192
- | [Continue](https://github.com/continuedev/continue) | 🔲 Untested | Should workstandard Anthropic API |
211
+ | [Open WebUI](https://github.com/open-webui/open-webui) | Verified | OpenAI-compatible endpointsset base URL to `http://127.0.0.1:3456` |
212
+ | [Continue](https://github.com/continuedev/continue) | 🔲 Untested | OpenAI-compatible endpoints should work — set `apiBase` to `http://127.0.0.1:3456` |
193
213
 
194
214
  Tested an agent or built a plugin? [Open an issue](https://github.com/rynfar/meridian/issues) and we'll add it.
195
215
 
@@ -209,6 +229,7 @@ src/proxy/
209
229
  ├── errors.ts ← Error classification
210
230
  ├── models.ts ← Model mapping (sonnet/opus/haiku, agentMode)
211
231
  ├── tokenRefresh.ts ← Cross-platform OAuth token refresh
232
+ ├── openai.ts ← OpenAI ↔ Anthropic format translation (pure)
212
233
  ├── setup.ts ← OpenCode plugin configuration
213
234
  ├── session/
214
235
  │ ├── lineage.ts ← Per-message hashing, mutation classification (pure)
@@ -272,6 +293,8 @@ Implement the `AgentAdapter` interface in `src/proxy/adapters/`. See [`adapters/
272
293
  | `GET /` | Landing page |
273
294
  | `POST /v1/messages` | Anthropic Messages API |
274
295
  | `POST /messages` | Alias for `/v1/messages` |
296
+ | `POST /v1/chat/completions` | OpenAI-compatible chat completions |
297
+ | `GET /v1/models` | OpenAI-compatible model list |
275
298
  | `GET /health` | Auth status, mode, plugin status |
276
299
  | `POST /auth/refresh` | Manually refresh the OAuth token |
277
300
  | `GET /telemetry` | Performance dashboard |
@@ -7128,6 +7128,146 @@ function isClosedControllerError(error) {
7128
7128
  return error.message.includes("Controller is already closed");
7129
7129
  }
7130
7130
 
7131
+ // src/proxy/openai.ts
7132
+ function extractOpenAiContent(content) {
7133
+ if (typeof content === "string")
7134
+ return content;
7135
+ return content.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
7136
+ }
7137
+ function translateOpenAiToAnthropic(body) {
7138
+ const messages = body.messages ?? [];
7139
+ if (messages.length === 0)
7140
+ return null;
7141
+ const systemParts = [];
7142
+ const turns = [];
7143
+ for (const msg of messages) {
7144
+ const text = extractOpenAiContent(msg.content ?? "");
7145
+ if (msg.role === "system") {
7146
+ if (text)
7147
+ systemParts.push(text);
7148
+ } else {
7149
+ turns.push({
7150
+ role: msg.role === "assistant" ? "assistant" : "user",
7151
+ content: text
7152
+ });
7153
+ }
7154
+ }
7155
+ let systemPrompt = systemParts.join(`
7156
+ `);
7157
+ let messagesToSend = turns;
7158
+ if (turns.length > 1) {
7159
+ const history = turns.slice(0, -1).map((m) => `${m.role}: ${m.content}`).join(`
7160
+ `);
7161
+ const historyBlock = `<conversation_history>
7162
+ ${history}
7163
+ </conversation_history>
7164
+
7165
+ ` + `Continue this conversation naturally. Respond to the user's latest message.`;
7166
+ systemPrompt = systemPrompt ? `${systemPrompt}
7167
+
7168
+ ${historyBlock}` : historyBlock;
7169
+ messagesToSend = turns.slice(-1);
7170
+ }
7171
+ const result = {
7172
+ model: body.model ?? "claude-sonnet-4-6",
7173
+ messages: messagesToSend,
7174
+ max_tokens: body.max_tokens ?? body.max_completion_tokens ?? 8192,
7175
+ stream: body.stream ?? false
7176
+ };
7177
+ if (systemPrompt)
7178
+ result.system = systemPrompt;
7179
+ if (body.temperature !== undefined)
7180
+ result.temperature = body.temperature;
7181
+ if (body.top_p !== undefined)
7182
+ result.top_p = body.top_p;
7183
+ return result;
7184
+ }
7185
+ function toFinishReason(stopReason) {
7186
+ if (stopReason === "max_tokens")
7187
+ return "length";
7188
+ return "stop";
7189
+ }
7190
+ function translateAnthropicToOpenAi(response, completionId, model, created) {
7191
+ const content = (response.content ?? []).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
7192
+ const promptTokens = response.usage?.input_tokens ?? 0;
7193
+ const completionTokens = response.usage?.output_tokens ?? 0;
7194
+ return {
7195
+ id: completionId,
7196
+ object: "chat.completion",
7197
+ created,
7198
+ model,
7199
+ choices: [{
7200
+ index: 0,
7201
+ message: { role: "assistant", content },
7202
+ finish_reason: toFinishReason(response.stop_reason)
7203
+ }],
7204
+ usage: {
7205
+ prompt_tokens: promptTokens,
7206
+ completion_tokens: completionTokens,
7207
+ total_tokens: promptTokens + completionTokens
7208
+ }
7209
+ };
7210
+ }
7211
+ function translateAnthropicSseEvent(event, completionId, model, created) {
7212
+ if (event.type === "message_start") {
7213
+ return {
7214
+ id: completionId,
7215
+ object: "chat.completion.chunk",
7216
+ created,
7217
+ model,
7218
+ choices: [{ index: 0, delta: { role: "assistant", content: "" }, finish_reason: null }]
7219
+ };
7220
+ }
7221
+ if (event.type === "content_block_delta" && event.delta?.type === "text_delta" && typeof event.delta.text === "string") {
7222
+ return {
7223
+ id: completionId,
7224
+ object: "chat.completion.chunk",
7225
+ created,
7226
+ model,
7227
+ choices: [{ index: 0, delta: { content: event.delta.text }, finish_reason: null }]
7228
+ };
7229
+ }
7230
+ if (event.type === "message_delta" && event.delta?.stop_reason) {
7231
+ return {
7232
+ id: completionId,
7233
+ object: "chat.completion.chunk",
7234
+ created,
7235
+ model,
7236
+ choices: [{ index: 0, delta: {}, finish_reason: toFinishReason(event.delta.stop_reason) }]
7237
+ };
7238
+ }
7239
+ return null;
7240
+ }
7241
+ function buildModelList(isMaxSubscription, now = Math.floor(Date.now() / 1000)) {
7242
+ const extendedContext = isMaxSubscription ? 1e6 : 200000;
7243
+ return [
7244
+ {
7245
+ id: "claude-sonnet-4-6",
7246
+ object: "model",
7247
+ created: now,
7248
+ owned_by: "anthropic",
7249
+ display_name: "Claude Sonnet 4.6",
7250
+ context_window: extendedContext
7251
+ },
7252
+ {
7253
+ id: "claude-opus-4-6",
7254
+ object: "model",
7255
+ created: now,
7256
+ owned_by: "anthropic",
7257
+ display_name: "Claude Opus 4.6",
7258
+ context_window: extendedContext
7259
+ },
7260
+ {
7261
+ id: "claude-haiku-4-5-20251001",
7262
+ object: "model",
7263
+ created: now,
7264
+ owned_by: "anthropic",
7265
+ display_name: "Claude Haiku 4.5",
7266
+ context_window: 200000
7267
+ }
7268
+ ];
7269
+ }
7270
+
7131
7271
  // src/proxy/messages.ts
7132
7272
  function stripCacheControlForHashing(obj) {
7133
7273
  if (!obj || typeof obj !== "object")
@@ -13465,7 +13605,11 @@ function buildQueryOptions(ctx) {
13465
13605
  undoRollbackUuid,
13466
13606
  sdkHooks,
13467
13607
  adapter,
13468
- onStderr
13608
+ onStderr,
13609
+ effort,
13610
+ thinking,
13611
+ taskBudget,
13612
+ betas
13469
13613
  } = ctx;
13470
13614
  const blockedTools = [...adapter.getBlockedBuiltinTools(), ...adapter.getAgentIncompatibleTools()];
13471
13615
  const mcpServerName = adapter.getMcpServerName();
@@ -13473,6 +13617,7 @@ function buildQueryOptions(ctx) {
13473
13617
  return {
13474
13618
  prompt,
13475
13619
  options: {
13620
+ executable: "node",
13476
13621
  maxTurns: passthrough ? 2 : 200,
13477
13622
  cwd: workingDirectory,
13478
13623
  model,
@@ -13504,7 +13649,11 @@ function buildQueryOptions(ctx) {
13504
13649
  ...Object.keys(sdkAgents).length > 0 ? { agents: sdkAgents } : {},
13505
13650
  ...resumeSessionId ? { resume: resumeSessionId } : {},
13506
13651
  ...isUndo ? { forkSession: true, ...undoRollbackUuid ? { resumeSessionAt: undoRollbackUuid } : {} } : {},
13507
- ...sdkHooks ? { hooks: sdkHooks } : {}
13652
+ ...sdkHooks ? { hooks: sdkHooks } : {},
13653
+ ...effort ? { effort } : {},
13654
+ ...thinking ? { thinking } : {},
13655
+ ...taskBudget ? { taskBudget } : {},
13656
+ ...betas && betas.length > 0 ? { betas } : {}
13508
13657
  }
13509
13658
  };
13510
13659
  }
@@ -13784,7 +13933,19 @@ function lookupSharedSession(key) {
13784
13933
  const store = readStore();
13785
13934
  return store[key];
13786
13935
  }
13787
- function storeSharedSession(key, claudeSessionId, messageCount, lineageHash, messageHashes, sdkMessageUuids) {
13936
+ function lookupSharedSessionByClaudeId(claudeSessionId) {
13937
+ const sessions = Object.values(readStore());
13938
+ let newest;
13939
+ for (const session of sessions) {
13940
+ if (session.claudeSessionId !== claudeSessionId)
13941
+ continue;
13942
+ if (!newest || session.lastUsedAt > newest.lastUsedAt) {
13943
+ newest = session;
13944
+ }
13945
+ }
13946
+ return newest;
13947
+ }
13948
+ function storeSharedSession(key, claudeSessionId, messageCount, lineageHash, messageHashes, sdkMessageUuids, contextUsage) {
13788
13949
  const path3 = getStorePath();
13789
13950
  const lockPath = `${path3}.lock`;
13790
13951
  const hasLock = skipLocking ? false : acquireLock(lockPath);
@@ -13801,7 +13962,8 @@ function storeSharedSession(key, claudeSessionId, messageCount, lineageHash, mes
13801
13962
  messageCount: messageCount ?? existing?.messageCount ?? 0,
13802
13963
  lineageHash: lineageHash ?? existing?.lineageHash,
13803
13964
  messageHashes: messageHashes ?? existing?.messageHashes,
13804
- sdkMessageUuids: sdkMessageUuids ?? existing?.sdkMessageUuids
13965
+ sdkMessageUuids: sdkMessageUuids ?? existing?.sdkMessageUuids,
13966
+ contextUsage: contextUsage ?? existing?.contextUsage
13805
13967
  };
13806
13968
  const maxEntries = getMaxStoredSessions();
13807
13969
  const keys = Object.keys(store);
@@ -13947,7 +14109,8 @@ function lookupSession(sessionId, messages, workingDirectory) {
13947
14109
  messageCount: shared.messageCount || 0,
13948
14110
  lineageHash: shared.lineageHash || "",
13949
14111
  messageHashes: shared.messageHashes,
13950
- sdkMessageUuids: shared.sdkMessageUuids
14112
+ sdkMessageUuids: shared.sdkMessageUuids,
14113
+ contextUsage: shared.contextUsage
13951
14114
  };
13952
14115
  const result = verifyLineage(state, messages, sessionId, sessionCache);
13953
14116
  if (result.type === "continuation" || result.type === "compaction") {
@@ -13974,7 +14137,8 @@ function lookupSession(sessionId, messages, workingDirectory) {
13974
14137
  messageCount: shared.messageCount || 0,
13975
14138
  lineageHash: shared.lineageHash || "",
13976
14139
  messageHashes: shared.messageHashes,
13977
- sdkMessageUuids: shared.sdkMessageUuids
14140
+ sdkMessageUuids: shared.sdkMessageUuids,
14141
+ contextUsage: shared.contextUsage
13978
14142
  };
13979
14143
  const result = verifyLineage(state, messages, fp, fingerprintCache);
13980
14144
  if (result.type === "continuation" || result.type === "compaction") {
@@ -13985,7 +14149,34 @@ function lookupSession(sessionId, messages, workingDirectory) {
13985
14149
  }
13986
14150
  return { type: "diverged" };
13987
14151
  }
13988
- function storeSession(sessionId, messages, claudeSessionId, workingDirectory, sdkMessageUuids) {
14152
+ function getSessionByClaudeId(claudeSessionId) {
14153
+ let newest;
14154
+ const consider = (state) => {
14155
+ if (!state || state.claudeSessionId !== claudeSessionId)
14156
+ return;
14157
+ if (!newest || state.lastAccess > newest.lastAccess) {
14158
+ newest = state;
14159
+ }
14160
+ };
14161
+ for (const state of sessionCache.values())
14162
+ consider(state);
14163
+ for (const state of fingerprintCache.values())
14164
+ consider(state);
14165
+ const shared = lookupSharedSessionByClaudeId(claudeSessionId);
14166
+ if (shared) {
14167
+ consider({
14168
+ claudeSessionId: shared.claudeSessionId,
14169
+ lastAccess: shared.lastUsedAt,
14170
+ messageCount: shared.messageCount || 0,
14171
+ lineageHash: shared.lineageHash || "",
14172
+ messageHashes: shared.messageHashes,
14173
+ sdkMessageUuids: shared.sdkMessageUuids,
14174
+ contextUsage: shared.contextUsage
14175
+ });
14176
+ }
14177
+ return newest;
14178
+ }
14179
+ function storeSession(sessionId, messages, claudeSessionId, workingDirectory, sdkMessageUuids, contextUsage) {
13989
14180
  if (!claudeSessionId)
13990
14181
  return;
13991
14182
  const lineageHash = computeLineageHash(messages);
@@ -13996,7 +14187,8 @@ function storeSession(sessionId, messages, claudeSessionId, workingDirectory, sd
13996
14187
  messageCount: messages?.length || 0,
13997
14188
  lineageHash,
13998
14189
  messageHashes,
13999
- sdkMessageUuids
14190
+ sdkMessageUuids,
14191
+ ...contextUsage ? { contextUsage } : {}
14000
14192
  };
14001
14193
  if (sessionId)
14002
14194
  sessionCache.set(sessionId, state);
@@ -14004,8 +14196,9 @@ function storeSession(sessionId, messages, claudeSessionId, workingDirectory, sd
14004
14196
  if (fp)
14005
14197
  fingerprintCache.set(fp, state);
14006
14198
  const key = sessionId || fp;
14007
- if (key)
14008
- storeSharedSession(key, claudeSessionId, state.messageCount, lineageHash, messageHashes, sdkMessageUuids);
14199
+ if (key) {
14200
+ storeSharedSession(key, claudeSessionId, state.messageCount, lineageHash, messageHashes, sdkMessageUuids, contextUsage);
14201
+ }
14009
14202
  }
14010
14203
 
14011
14204
  // src/proxy/server.ts
@@ -14083,6 +14276,16 @@ function buildFreshPrompt(messages, stripCacheControl) {
14083
14276
 
14084
14277
  `) || "";
14085
14278
  }
14279
+ function logUsage(requestId, usage) {
14280
+ const fmt = (n) => n > 1000 ? `${Math.round(n / 1000)}k` : String(n);
14281
+ const parts = [
14282
+ `input=${fmt(usage.input_tokens ?? 0)}`,
14283
+ `output=${fmt(usage.output_tokens ?? 0)}`,
14284
+ ...usage.cache_read_input_tokens ? [`cache_read=${fmt(usage.cache_read_input_tokens)}`] : [],
14285
+ ...usage.cache_creation_input_tokens ? [`cache_write=${fmt(usage.cache_creation_input_tokens)}`] : []
14286
+ ];
14287
+ console.error(`[PROXY] ${requestId} usage: ${parts.join(" ")}`);
14288
+ }
14086
14289
  function createProxyServer(config = {}) {
14087
14290
  const finalConfig = { ...DEFAULT_PROXY_CONFIG, ...config };
14088
14291
  const app = new Hono2;
@@ -14094,7 +14297,7 @@ function createProxyServer(config = {}) {
14094
14297
  status: "ok",
14095
14298
  service: "meridian",
14096
14299
  format: "anthropic",
14097
- endpoints: ["/v1/messages", "/messages", "/telemetry", "/health"]
14300
+ endpoints: ["/v1/messages", "/messages", "/v1/chat/completions", "/v1/models", "/telemetry", "/health"]
14098
14301
  });
14099
14302
  }
14100
14303
  return c.html(landingHtml);
@@ -14170,6 +14373,22 @@ function createProxyServer(config = {}) {
14170
14373
  `);
14171
14374
  }
14172
14375
  }
14376
+ const effortHeader = c.req.header("x-opencode-effort");
14377
+ const thinkingHeader = c.req.header("x-opencode-thinking");
14378
+ const taskBudgetHeader = c.req.header("x-opencode-task-budget");
14379
+ const betaHeader = c.req.header("anthropic-beta");
14380
+ const effort = effortHeader || body.effort || undefined;
14381
+ let thinking = body.thinking || undefined;
14382
+ if (thinkingHeader !== undefined) {
14383
+ try {
14384
+ thinking = JSON.parse(thinkingHeader);
14385
+ } catch (e) {
14386
+ console.error(`[PROXY] ${requestMeta.requestId} ignoring malformed x-opencode-thinking header: ${e instanceof Error ? e.message : String(e)}`);
14387
+ }
14388
+ }
14389
+ const parsedBudget = taskBudgetHeader ? Number.parseInt(taskBudgetHeader, 10) : NaN;
14390
+ const taskBudget = Number.isFinite(parsedBudget) ? { total: parsedBudget } : body.task_budget ? { total: body.task_budget.total ?? body.task_budget } : undefined;
14391
+ const betas = betaHeader ? betaHeader.split(",").map((b) => b.trim()).filter(Boolean) : undefined;
14173
14392
  const agentSessionId = adapter.getSessionId(c);
14174
14393
  const lineageResult = lookupSession(agentSessionId, body.messages || [], workingDirectory);
14175
14394
  const isResume = lineageResult.type === "continuation" || lineageResult.type === "compaction";
@@ -14343,6 +14562,7 @@ function createProxyServer(config = {}) {
14343
14562
  while (sdkUuidMap.length < allMessages.length)
14344
14563
  sdkUuidMap.push(null);
14345
14564
  claudeLog("upstream.start", { mode: "non_stream", model });
14565
+ let lastUsage;
14346
14566
  try {
14347
14567
  if (!claudeExecutable) {
14348
14568
  claudeExecutable = await resolveClaudeExecutableAsync();
@@ -14371,7 +14591,11 @@ function createProxyServer(config = {}) {
14371
14591
  undoRollbackUuid,
14372
14592
  sdkHooks,
14373
14593
  adapter,
14374
- onStderr
14594
+ onStderr,
14595
+ effort,
14596
+ thinking,
14597
+ taskBudget,
14598
+ betas
14375
14599
  }))) {
14376
14600
  if (event.type === "assistant" && !event.error) {
14377
14601
  didYieldContent = true;
@@ -14410,7 +14634,11 @@ function createProxyServer(config = {}) {
14410
14634
  undoRollbackUuid: undefined,
14411
14635
  sdkHooks,
14412
14636
  adapter,
14413
- onStderr
14637
+ onStderr,
14638
+ effort,
14639
+ thinking,
14640
+ taskBudget,
14641
+ betas
14414
14642
  }));
14415
14643
  return;
14416
14644
  }
@@ -14492,6 +14720,9 @@ function createProxyServer(config = {}) {
14492
14720
  }
14493
14721
  contentBlocks.push(b);
14494
14722
  }
14723
+ const msgUsage = message.message.usage;
14724
+ if (msgUsage)
14725
+ lastUsage = { ...lastUsage, ...msgUsage };
14495
14726
  }
14496
14727
  }
14497
14728
  claudeLog("upstream.completed", {
@@ -14500,6 +14731,8 @@ function createProxyServer(config = {}) {
14500
14731
  assistantMessages,
14501
14732
  durationMs: Date.now() - upstreamStartAt
14502
14733
  });
14734
+ if (lastUsage)
14735
+ logUsage(requestMeta.requestId, lastUsage);
14503
14736
  } catch (error) {
14504
14737
  const stderrOutput = stderrLines.join(`
14505
14738
  `).trim();
@@ -14584,7 +14817,7 @@ Subprocess stderr: ${stderrOutput}`;
14584
14817
  error: null
14585
14818
  });
14586
14819
  if (currentSessionId) {
14587
- storeSession(agentSessionId, body.messages || [], currentSessionId, workingDirectory, sdkUuidMap);
14820
+ storeSession(agentSessionId, body.messages || [], currentSessionId, workingDirectory, sdkUuidMap, lastUsage);
14588
14821
  }
14589
14822
  const responseSessionId = currentSessionId || resumeSessionId || `session_${Date.now()}`;
14590
14823
  return new Response(JSON.stringify({
@@ -14638,6 +14871,7 @@ Subprocess stderr: ${stderrOutput}`;
14638
14871
  while (sdkUuidMap.length < allMessages.length)
14639
14872
  sdkUuidMap.push(null);
14640
14873
  let messageStartEmitted = false;
14874
+ let lastUsage;
14641
14875
  try {
14642
14876
  let currentSessionId;
14643
14877
  const MAX_RATE_LIMIT_RETRIES = 2;
@@ -14664,7 +14898,11 @@ Subprocess stderr: ${stderrOutput}`;
14664
14898
  undoRollbackUuid,
14665
14899
  sdkHooks,
14666
14900
  adapter,
14667
- onStderr
14901
+ onStderr,
14902
+ effort,
14903
+ thinking,
14904
+ taskBudget,
14905
+ betas
14668
14906
  }))) {
14669
14907
  if (event.type === "stream_event") {
14670
14908
  didYieldClientEvent = true;
@@ -14703,7 +14941,11 @@ Subprocess stderr: ${stderrOutput}`;
14703
14941
  undoRollbackUuid: undefined,
14704
14942
  sdkHooks,
14705
14943
  adapter,
14706
- onStderr
14944
+ onStderr,
14945
+ effort,
14946
+ thinking,
14947
+ taskBudget,
14948
+ betas
14707
14949
  }));
14708
14950
  return;
14709
14951
  }
@@ -14813,6 +15055,9 @@ Subprocess stderr: ${stderrOutput}`;
14813
15055
  if (eventType === "message_start") {
14814
15056
  skipBlockIndices.clear();
14815
15057
  sdkToClientIndex.clear();
15058
+ const startUsage = event.message?.usage;
15059
+ if (startUsage)
15060
+ lastUsage = { ...lastUsage, ...startUsage };
14816
15061
  if (messageStartEmitted) {
14817
15062
  continue;
14818
15063
  }
@@ -14845,6 +15090,9 @@ Subprocess stderr: ${stderrOutput}`;
14845
15090
  event.index = sdkToClientIndex.get(eventIndex);
14846
15091
  }
14847
15092
  if (eventType === "message_delta") {
15093
+ const deltaUsage = event.usage;
15094
+ if (deltaUsage)
15095
+ lastUsage = { ...lastUsage, ...deltaUsage };
14848
15096
  const stopReason = event.delta?.stop_reason;
14849
15097
  if (stopReason === "tool_use" && skipBlockIndices.size > 0) {
14850
15098
  continue;
@@ -14886,8 +15134,10 @@ data: ${JSON.stringify({ type: "message_stop" })}
14886
15134
  eventsForwarded,
14887
15135
  textEventsForwarded
14888
15136
  });
15137
+ if (lastUsage)
15138
+ logUsage(requestMeta.requestId, lastUsage);
14889
15139
  if (currentSessionId) {
14890
- storeSession(agentSessionId, body.messages || [], currentSessionId, workingDirectory, sdkUuidMap);
15140
+ storeSession(agentSessionId, body.messages || [], currentSessionId, workingDirectory, sdkUuidMap, lastUsage);
14891
15141
  }
14892
15142
  if (!streamClosed) {
14893
15143
  const unseenToolUses = capturedToolUses.filter((tu) => !streamedToolUseIds.has(tu.id));
@@ -15180,6 +15430,105 @@ data: ${JSON.stringify({
15180
15430
  }
15181
15431
  return c.json({ success: false, message: "Token refresh failed. If the problem persists, run 'claude login'." }, 500);
15182
15432
  });
15433
+ app.post("/v1/chat/completions", async (c) => {
15434
+ const rawBody = await c.req.json();
15435
+ const anthropicBody = translateOpenAiToAnthropic(rawBody);
15436
+ if (!anthropicBody) {
15437
+ return c.json({ type: "error", error: { type: "invalid_request_error", message: "messages: Field required" } }, 400);
15438
+ }
15439
+ const internalReq = new Request("http://internal/v1/messages", {
15440
+ method: "POST",
15441
+ headers: { "Content-Type": "application/json" },
15442
+ body: JSON.stringify(anthropicBody)
15443
+ });
15444
+ const internalRes = await app.fetch(internalReq);
15445
+ if (!internalRes.ok) {
15446
+ const errBody = await internalRes.text();
15447
+ return c.json({ type: "error", error: { type: "upstream_error", message: errBody } }, internalRes.status);
15448
+ }
15449
+ const completionId = `chatcmpl-${randomUUID()}`;
15450
+ const created = Math.floor(Date.now() / 1000);
15451
+ const model = typeof rawBody.model === "string" && rawBody.model ? rawBody.model : "claude-sonnet-4-6";
15452
+ if (!anthropicBody.stream) {
15453
+ const anthropicRes = await internalRes.json();
15454
+ return c.json(translateAnthropicToOpenAi(anthropicRes, completionId, model, created));
15455
+ }
15456
+ const encoder = new TextEncoder;
15457
+ const readable = new ReadableStream({
15458
+ async start(controller) {
15459
+ const reader = internalRes.body?.getReader();
15460
+ if (!reader) {
15461
+ controller.close();
15462
+ return;
15463
+ }
15464
+ const decoder = new TextDecoder;
15465
+ let buffer = "";
15466
+ let streamError = null;
15467
+ try {
15468
+ while (true) {
15469
+ const { done, value } = await reader.read();
15470
+ if (done)
15471
+ break;
15472
+ buffer += decoder.decode(value, { stream: true });
15473
+ const lines = buffer.split(`
15474
+ `);
15475
+ buffer = lines.pop() ?? "";
15476
+ for (const line of lines) {
15477
+ if (!line.startsWith("data: "))
15478
+ continue;
15479
+ const dataStr = line.slice(6).trim();
15480
+ if (!dataStr)
15481
+ continue;
15482
+ let event;
15483
+ try {
15484
+ event = JSON.parse(dataStr);
15485
+ } catch {
15486
+ continue;
15487
+ }
15488
+ if (typeof event.type !== "string")
15489
+ continue;
15490
+ const chunk = translateAnthropicSseEvent(event, completionId, model, created);
15491
+ if (chunk)
15492
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}
15493
+
15494
+ `));
15495
+ }
15496
+ }
15497
+ } catch (err) {
15498
+ streamError = err instanceof Error ? err : new Error(String(err));
15499
+ } finally {
15500
+ if (!streamError)
15501
+ controller.enqueue(encoder.encode(`data: [DONE]
15502
+
15503
+ `));
15504
+ controller.close();
15505
+ }
15506
+ }
15507
+ });
15508
+ return new Response(readable, {
15509
+ headers: {
15510
+ "Content-Type": "text/event-stream",
15511
+ "Cache-Control": "no-cache",
15512
+ Connection: "keep-alive"
15513
+ }
15514
+ });
15515
+ });
15516
+ app.get("/v1/models", async (c) => {
15517
+ const authStatus = await getClaudeAuthStatusAsync();
15518
+ const isMax = authStatus?.subscriptionType === "max";
15519
+ return c.json({ object: "list", data: buildModelList(isMax) });
15520
+ });
15521
+ app.get("/v1/sessions/:claudeSessionId/context-usage", (c) => {
15522
+ const claudeSessionId = c.req.param("claudeSessionId");
15523
+ const session = getSessionByClaudeId(claudeSessionId);
15524
+ if (!session) {
15525
+ return c.json({ error: "Session not found" }, 404);
15526
+ }
15527
+ if (!session.contextUsage) {
15528
+ return c.json({ error: "No usage data available for this session" }, 404);
15529
+ }
15530
+ return c.json({ session_id: claudeSessionId, context_usage: session.contextUsage });
15531
+ });
15183
15532
  app.all("*", (c) => {
15184
15533
  console.error(`[PROXY] UNHANDLED ${c.req.method} ${c.req.url}`);
15185
15534
  return c.json({ error: { type: "not_found", message: `Endpoint not supported: ${c.req.method} ${new URL(c.req.url).pathname}` } }, 404);
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startProxyServer
4
- } from "./cli-s6f9jefk.js";
4
+ } from "./cli-jk1p4nw8.js";
5
5
  import"./cli-rtab0qa6.js";
6
6
  import"./cli-m9pfb7h9.js";
7
7
  import {
@@ -0,0 +1,142 @@
1
+ /**
2
+ * OpenAI ↔ Anthropic format translation.
3
+ *
4
+ * Pure functions — no I/O, no side effects. Used by the /v1/chat/completions
5
+ * and /v1/models routes in server.ts.
6
+ *
7
+ * Design note: OpenAI clients always send the full conversation history on
8
+ * every request. Feeding that directly into Meridian's session system would
9
+ * classify every turn as "undo" or "diverged" (since the message list keeps
10
+ * changing). Instead:
11
+ * 1. The last user message becomes the actual SDK request
12
+ * 2. Prior turns are packed into a <conversation_history> block in the
13
+ * system prompt so Claude has context
14
+ * 3. Each chat completions request gets a fresh SDK session
15
+ * This is intentional — OpenAI-format clients replay full history themselves
16
+ * and don't benefit from Meridian's session resumption.
17
+ */
18
+ export type OpenAiRole = "system" | "user" | "assistant";
19
+ export interface OpenAiContentPart {
20
+ type: string;
21
+ text?: string;
22
+ }
23
+ export interface OpenAiMessage {
24
+ role: OpenAiRole;
25
+ content: string | OpenAiContentPart[];
26
+ }
27
+ export interface OpenAiChatRequest {
28
+ model?: string;
29
+ messages?: OpenAiMessage[];
30
+ stream?: boolean;
31
+ max_tokens?: number;
32
+ max_completion_tokens?: number;
33
+ temperature?: number;
34
+ top_p?: number;
35
+ }
36
+ export interface AnthropicMessage {
37
+ role: "user" | "assistant";
38
+ content: string;
39
+ }
40
+ export interface AnthropicRequestBody {
41
+ model: string;
42
+ messages: AnthropicMessage[];
43
+ max_tokens: number;
44
+ stream: boolean;
45
+ system?: string;
46
+ temperature?: number;
47
+ top_p?: number;
48
+ }
49
+ export interface AnthropicUsage {
50
+ input_tokens?: number;
51
+ output_tokens?: number;
52
+ }
53
+ export interface AnthropicContentBlock {
54
+ type: string;
55
+ text?: string;
56
+ }
57
+ export interface AnthropicResponse {
58
+ content?: AnthropicContentBlock[];
59
+ stop_reason?: string;
60
+ usage?: AnthropicUsage;
61
+ }
62
+ export interface OpenAiStreamChunk {
63
+ id: string;
64
+ object: "chat.completion.chunk";
65
+ created: number;
66
+ model: string;
67
+ choices: Array<{
68
+ index: 0;
69
+ delta: {
70
+ role?: "assistant";
71
+ content?: string;
72
+ };
73
+ finish_reason: "stop" | "length" | null;
74
+ }>;
75
+ }
76
+ export interface OpenAiCompletion {
77
+ id: string;
78
+ object: "chat.completion";
79
+ created: number;
80
+ model: string;
81
+ choices: Array<{
82
+ index: 0;
83
+ message: {
84
+ role: "assistant";
85
+ content: string;
86
+ };
87
+ finish_reason: "stop" | "length";
88
+ }>;
89
+ usage: {
90
+ prompt_tokens: number;
91
+ completion_tokens: number;
92
+ total_tokens: number;
93
+ };
94
+ }
95
+ export interface OpenAiModel {
96
+ id: string;
97
+ object: "model";
98
+ created: number;
99
+ owned_by: string;
100
+ display_name: string;
101
+ context_window: number;
102
+ }
103
+ /**
104
+ * Normalise an OpenAI message content field to a plain string.
105
+ * Handles both string content and structured content arrays.
106
+ */
107
+ export declare function extractOpenAiContent(content: string | OpenAiContentPart[]): string;
108
+ /**
109
+ * Translate an OpenAI /v1/chat/completions request body into an Anthropic
110
+ * /v1/messages request body.
111
+ *
112
+ * Returns null if the request has no messages (caller should return 400).
113
+ */
114
+ export declare function translateOpenAiToAnthropic(body: OpenAiChatRequest): AnthropicRequestBody | null;
115
+ /**
116
+ * Translate a complete Anthropic /v1/messages response to OpenAI format.
117
+ * Thinking blocks are filtered out — only text blocks are included.
118
+ */
119
+ export declare function translateAnthropicToOpenAi(response: AnthropicResponse, completionId: string, model: string, created: number): OpenAiCompletion;
120
+ interface AnthropicSseEvent {
121
+ type: string;
122
+ delta?: {
123
+ type?: string;
124
+ text?: string;
125
+ stop_reason?: string;
126
+ };
127
+ message?: {
128
+ id?: string;
129
+ };
130
+ }
131
+ /**
132
+ * Translate one parsed Anthropic SSE event into an OpenAI stream chunk.
133
+ * Returns null for events that should be skipped (pings, block starts, etc).
134
+ */
135
+ export declare function translateAnthropicSseEvent(event: AnthropicSseEvent, completionId: string, model: string, created: number): OpenAiStreamChunk | null;
136
+ /**
137
+ * Return the static list of available Claude models in OpenAI format.
138
+ * Context windows reflect subscription capabilities.
139
+ */
140
+ export declare function buildModelList(isMaxSubscription: boolean, now?: number): OpenAiModel[];
141
+ export {};
142
+ //# sourceMappingURL=openai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/proxy/openai.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@
5
5
  * between the streaming and non-streaming paths in server.ts.
6
6
  */
7
7
  import type { AgentAdapter } from "./adapter";
8
+ import type { Options } from "@anthropic-ai/claude-agent-sdk";
8
9
  import { createPassthroughMcpServer } from "./passthroughTools";
9
10
  export interface QueryContext {
10
11
  /** The prompt to send (text or async iterable for multimodal) */
@@ -39,73 +40,32 @@ export interface QueryContext {
39
40
  adapter: AgentAdapter;
40
41
  /** Callback to receive stderr lines from the Claude subprocess */
41
42
  onStderr?: (line: string) => void;
43
+ /** Effort level — controls thinking depth (low/medium/high/max) */
44
+ effort?: 'low' | 'medium' | 'high' | 'max';
45
+ /** Thinking configuration — adaptive, enabled with budget, or disabled */
46
+ thinking?: {
47
+ type: 'adaptive';
48
+ } | {
49
+ type: 'enabled';
50
+ budgetTokens?: number;
51
+ } | {
52
+ type: 'disabled';
53
+ };
54
+ /** API-side task budget in tokens — model paces tool use within this limit */
55
+ taskBudget?: {
56
+ total: number;
57
+ };
58
+ /** Beta features to enable */
59
+ betas?: string[];
42
60
  }
43
61
  /**
44
62
  * Build the options object for the Claude Agent SDK query() call.
45
63
  * This is called identically from both streaming and non-streaming paths,
46
64
  * with the only difference being `includePartialMessages` for streaming.
47
65
  */
48
- export declare function buildQueryOptions(ctx: QueryContext): {
49
- prompt: string | AsyncIterable<any>;
50
- options: {
51
- hooks?: any;
52
- resumeSessionAt?: string | undefined;
53
- forkSession?: boolean | undefined;
54
- resume?: string | undefined;
55
- agents?: Record<string, any> | undefined;
56
- env: {
57
- ENABLE_CLAUDEAI_MCP_SERVERS?: string | undefined;
58
- ENABLE_TOOL_SEARCH: string;
59
- };
60
- stderr?: ((line: string) => void) | undefined;
61
- plugins: never[];
62
- allowedTools?: string[] | undefined;
63
- mcpServers?: {
64
- oc: import("@anthropic-ai/claude-agent-sdk").McpSdkServerConfigWithInstance;
65
- } | undefined;
66
- disallowedTools: string[];
67
- systemPrompt?: string | {
68
- type: "preset";
69
- preset: "claude_code";
70
- append: string;
71
- } | undefined;
72
- permissionMode: "bypassPermissions";
73
- allowDangerouslySkipPermissions: boolean;
74
- includePartialMessages?: boolean | undefined;
75
- maxTurns: number;
76
- cwd: string;
77
- model: string;
78
- pathToClaudeCodeExecutable: string;
79
- } | {
80
- hooks?: any;
81
- resumeSessionAt?: string | undefined;
82
- forkSession?: boolean | undefined;
83
- resume?: string | undefined;
84
- agents?: Record<string, any> | undefined;
85
- env: {
86
- ENABLE_CLAUDEAI_MCP_SERVERS?: string | undefined;
87
- ENABLE_TOOL_SEARCH: string;
88
- };
89
- stderr?: ((line: string) => void) | undefined;
90
- plugins: never[];
91
- disallowedTools: string[];
92
- allowedTools: string[];
93
- mcpServers: {
94
- [x: string]: import("@anthropic-ai/claude-agent-sdk").McpSdkServerConfigWithInstance;
95
- oc?: undefined;
96
- };
97
- systemPrompt?: string | {
98
- type: "preset";
99
- preset: "claude_code";
100
- append: string;
101
- } | undefined;
102
- permissionMode: "bypassPermissions";
103
- allowDangerouslySkipPermissions: boolean;
104
- includePartialMessages?: boolean | undefined;
105
- maxTurns: number;
106
- cwd: string;
107
- model: string;
108
- pathToClaudeCodeExecutable: string;
109
- };
110
- };
66
+ export interface BuildQueryResult {
67
+ prompt: QueryContext["prompt"];
68
+ options: Options;
69
+ }
70
+ export declare function buildQueryOptions(ctx: QueryContext): BuildQueryResult;
111
71
  //# sourceMappingURL=query.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/proxy/query.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAE7C,OAAO,EAAE,0BAA0B,EAAwB,MAAM,oBAAoB,CAAA;AAErF,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAA;IACnC,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,+BAA+B;IAC/B,gBAAgB,EAAE,MAAM,CAAA;IACxB,yCAAyC;IACzC,aAAa,EAAE,MAAM,CAAA;IACrB,gCAAgC;IAChC,gBAAgB,EAAE,MAAM,CAAA;IACxB,0CAA0C;IAC1C,WAAW,EAAE,OAAO,CAAA;IACpB,0CAA0C;IAC1C,MAAM,EAAE,OAAO,CAAA;IACf,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC9B,mEAAmE;IACnE,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAAA;IAC9D,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IAC5C,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,wCAAwC;IACxC,MAAM,EAAE,OAAO,CAAA;IACf,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,GAAG,CAAA;IACd,qDAAqD;IACrD,OAAO,EAAE,YAAY,CAAA;IACrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAClC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY;;;;;;;;;;;;yBAR/B,MAAM,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAAf,MAAM,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;EAiElC"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/proxy/query.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAW,MAAM,gCAAgC,CAAA;AAEtE,OAAO,EAAE,0BAA0B,EAAwB,MAAM,oBAAoB,CAAA;AAErF,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAA;IACnC,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,+BAA+B;IAC/B,gBAAgB,EAAE,MAAM,CAAA;IACxB,yCAAyC;IACzC,aAAa,EAAE,MAAM,CAAA;IACrB,gCAAgC;IAChC,gBAAgB,EAAE,MAAM,CAAA;IACxB,0CAA0C;IAC1C,WAAW,EAAE,OAAO,CAAA;IACpB,0CAA0C;IAC1C,MAAM,EAAE,OAAO,CAAA;IACf,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC9B,mEAAmE;IACnE,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAAA;IAC9D,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IAC5C,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,wCAAwC;IACxC,MAAM,EAAE,OAAO,CAAA;IACf,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,GAAG,CAAA;IACd,qDAAqD;IACrD,OAAO,EAAE,YAAY,CAAA;IACrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,mEAAmE;IACnE,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAA;IAC1C,0EAA0E;IAC1E,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,CAAA;IACnG,8EAA8E;IAC9E,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9B,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;IAC9B,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,gBAAgB,CAmErE"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAmBvD,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,KAAK,aAAa,EACnB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAA+B,iBAAiB,EAAE,mBAAmB,EAAgB,MAAM,iBAAiB,CAAA;AAEnH,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AAyF7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAyxChF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA0ChG"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAoBvD,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,KAAK,aAAa,EAEnB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAA+B,iBAAiB,EAAE,mBAAmB,EAAsC,MAAM,iBAAiB,CAAA;AAEzI,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AAoG7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAw7ChF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA0ChG"}
@@ -4,7 +4,7 @@
4
4
  * Manages in-memory LRU caches for session and fingerprint lookups,
5
5
  * coordinates with the shared file store for cross-proxy session resume.
6
6
  */
7
- import { type LineageResult } from "./lineage";
7
+ import { type SessionState, type TokenUsage, type LineageResult } from "./lineage";
8
8
  export declare function getMaxSessionsLimit(): number;
9
9
  /** Clear all session caches (used in tests).
10
10
  * Re-reads MERIDIAN_MAX_SESSIONS / CLAUDE_PROXY_MAX_SESSIONS so tests can override the limit. */
@@ -22,11 +22,16 @@ export declare function lookupSession(sessionId: string | undefined, messages: A
22
22
  role: string;
23
23
  content: any;
24
24
  }>, workingDirectory?: string): LineageResult;
25
+ /** Look up a session by the Claude SDK session ID returned in responses.
26
+ * Searches both in-memory caches and the shared file store, returning the
27
+ * freshest matching state if multiple cache keys point to the same Claude session. */
28
+ export declare function getSessionByClaudeId(claudeSessionId: string): SessionState | undefined;
25
29
  /** Store a session mapping with lineage hash and SDK UUIDs for divergence detection.
26
30
  * @param sdkMessageUuids — per-message SDK assistant UUIDs (null for user messages).
27
- * If provided, merged with any previously stored UUIDs to build a complete map. */
31
+ * If provided, merged with any previously stored UUIDs to build a complete map.
32
+ * @param contextUsage — optional last observed token usage to attach to the session. */
28
33
  export declare function storeSession(sessionId: string | undefined, messages: Array<{
29
34
  role: string;
30
- content: any;
31
- }>, claudeSessionId: string, workingDirectory?: string, sdkMessageUuids?: Array<string | null>): void;
35
+ content: unknown;
36
+ }>, claudeSessionId: string, workingDirectory?: string, sdkMessageUuids?: Array<string | null>, contextUsage?: TokenUsage): void;
32
37
  //# sourceMappingURL=cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/proxy/session/cache.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,WAAW,CAAA;AAMlB,wBAAgB,mBAAmB,IAAI,MAAM,CAW5C;AAqCD;kGACkG;AAClG,wBAAgB,iBAAiB,SAYhC;AAED;iFACiF;AACjF,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,gBAAgB,CAAC,EAAE,MAAM,EACzB,QAAQ,CAAC,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAC/C,IAAI,CAoBN;AAUD;;uDAEuD;AACvD,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,EAC/C,gBAAgB,CAAC,EAAE,MAAM,GACxB,aAAa,CAqDf;AAED;;sFAEsF;AACtF,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,EAC/C,eAAe,EAAE,MAAM,EACvB,gBAAgB,CAAC,EAAE,MAAM,EACzB,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,QAoBvC"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/proxy/session/cache.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,aAAa,EACnB,MAAM,WAAW,CAAA;AAMlB,wBAAgB,mBAAmB,IAAI,MAAM,CAW5C;AAqCD;kGACkG;AAClG,wBAAgB,iBAAiB,SAYhC;AAED;iFACiF;AACjF,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,gBAAgB,CAAC,EAAE,MAAM,EACzB,QAAQ,CAAC,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAC/C,IAAI,CAoBN;AAUD;;uDAEuD;AACvD,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,EAC/C,gBAAgB,CAAC,EAAE,MAAM,GACxB,aAAa,CAuDf;AAED;;uFAEuF;AACvF,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CA2BtF;AAED;;;yFAGyF;AACzF,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,EACnD,eAAe,EAAE,MAAM,EACvB,gBAAgB,CAAC,EAAE,MAAM,EACzB,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,EACtC,YAAY,CAAC,EAAE,UAAU,QA+B1B"}
@@ -4,6 +4,13 @@
4
4
  * Pure functions for hashing messages and classifying mutations
5
5
  * (continuation, compaction, undo, diverged).
6
6
  */
7
+ /** Token usage counters from the SDK (subset of Anthropic usage object). */
8
+ export interface TokenUsage {
9
+ input_tokens?: number;
10
+ output_tokens?: number;
11
+ cache_read_input_tokens?: number;
12
+ cache_creation_input_tokens?: number;
13
+ }
7
14
  /** Minimum suffix overlap (stored messages found at the end of incoming)
8
15
  * required to classify a mutation as compaction rather than a branch. */
9
16
  export declare const MIN_SUFFIX_FOR_COMPACTION = 2;
@@ -23,6 +30,8 @@ export interface SessionState {
23
30
  * Only assistant messages have UUIDs (user messages are null).
24
31
  * Used to find the rollback point for undo. */
25
32
  sdkMessageUuids?: Array<string | null>;
33
+ /** Last observed token usage for this session (from SDK message_start / message_delta events) */
34
+ contextUsage?: TokenUsage;
26
35
  }
27
36
  /**
28
37
  * Result of lineage verification — classifies the mutation and provides
@@ -1 +1 @@
1
- {"version":3,"file":"lineage.d.ts","sourceRoot":"","sources":["../../../src/proxy/session/lineage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH;0EAC0E;AAC1E,eAAO,MAAM,yBAAyB,IAAI,CAAA;AAE1C,MAAM,WAAW,YAAY;IAC3B,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB;;qDAEiD;IACjD,WAAW,EAAE,MAAM,CAAA;IACnB;;kCAE8B;IAC9B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;oDAEgD;IAChD,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;CACvC;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,YAAY,CAAC;IAAG,OAAO,EAAE,YAAY,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAS,OAAO,EAAE,YAAY,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACxG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAA;AAIxB;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,MAAM,CAI1F;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,GAAG,MAAM,CAK3E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,MAAM,EAAE,CAG9F;AAID;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAO7F;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAO7F;AAID,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAC7B;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,gBAAgB,GACtB,aAAa,CAqFf"}
1
+ {"version":3,"file":"lineage.d.ts","sourceRoot":"","sources":["../../../src/proxy/session/lineage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IACzB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,2BAA2B,CAAC,EAAE,MAAM,CAAA;CACrC;AAED;0EAC0E;AAC1E,eAAO,MAAM,yBAAyB,IAAI,CAAA;AAE1C,MAAM,WAAW,YAAY;IAC3B,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB;;qDAEiD;IACjD,WAAW,EAAE,MAAM,CAAA;IACnB;;kCAE8B;IAC9B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;oDAEgD;IAChD,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACtC,iGAAiG;IACjG,YAAY,CAAC,EAAE,UAAU,CAAA;CAC1B;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,YAAY,CAAC;IAAG,OAAO,EAAE,YAAY,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAS,OAAO,EAAE,YAAY,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACxG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAA;AAIxB;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,MAAM,CAI1F;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,GAAG,MAAM,CAK3E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,MAAM,EAAE,CAG9F;AAID;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAO7F;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAO7F;AAID,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAC7B;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,gBAAgB,GACtB,aAAa,CAqFf"}
@@ -9,6 +9,7 @@
9
9
  * Format: { [key]: { claudeSessionId, createdAt, lastUsedAt } }
10
10
  * Keys are either OpenCode session IDs or conversation fingerprints.
11
11
  */
12
+ import type { TokenUsage } from "./session/lineage";
12
13
  export interface StoredSession {
13
14
  claudeSessionId: string;
14
15
  createdAt: number;
@@ -20,6 +21,8 @@ export interface StoredSession {
20
21
  messageHashes?: string[];
21
22
  /** Per-message SDK assistant UUIDs for undo rollback (null for user messages) */
22
23
  sdkMessageUuids?: Array<string | null>;
24
+ /** Last observed token usage for this Claude session */
25
+ contextUsage?: TokenUsage;
23
26
  }
24
27
  /** Set an explicit session store directory. Takes priority over env var.
25
28
  * Pass null to clear. For testing only.
@@ -28,7 +31,8 @@ export declare function setSessionStoreDir(dir: string | null, opts?: {
28
31
  skipLocking?: boolean;
29
32
  }): void;
30
33
  export declare function lookupSharedSession(key: string): StoredSession | undefined;
31
- export declare function storeSharedSession(key: string, claudeSessionId: string, messageCount?: number, lineageHash?: string, messageHashes?: string[], sdkMessageUuids?: Array<string | null>): void;
34
+ export declare function lookupSharedSessionByClaudeId(claudeSessionId: string): StoredSession | undefined;
35
+ export declare function storeSharedSession(key: string, claudeSessionId: string, messageCount?: number, lineageHash?: string, messageHashes?: string[], sdkMessageUuids?: Array<string | null>, contextUsage?: TokenUsage): void;
32
36
  /** Remove a single session from the shared file store.
33
37
  * Used when a session is detected as stale (e.g. expired upstream). */
34
38
  export declare function evictSharedSession(key: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"sessionStore.d.ts","sourceRoot":"","sources":["../../src/proxy/sessionStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgBH,MAAM,WAAW,aAAa;IAC5B,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,iFAAiF;IACjF,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;CACvC;AA0DD;;oFAEoF;AACpF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAG7F;AAsED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAG1E;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,EAAE,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAqC5L;AAED;wEACwE;AACxE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAkBpD;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAO1C"}
1
+ {"version":3,"file":"sessionStore.d.ts","sourceRoot":"","sources":["../../src/proxy/sessionStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAeH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnD,MAAM,WAAW,aAAa;IAC5B,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,iFAAiF;IACjF,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACtC,wDAAwD;IACxD,YAAY,CAAC,EAAE,UAAU,CAAA;CAC1B;AA0DD;;oFAEoF;AACpF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAG7F;AAsED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAG1E;AAED,wBAAgB,6BAA6B,CAAC,eAAe,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAYhG;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,eAAe,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,EACpB,aAAa,CAAC,EAAE,MAAM,EAAE,EACxB,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,EACtC,YAAY,CAAC,EAAE,UAAU,GACxB,IAAI,CAsCN;AAED;wEACwE;AACxE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAkBpD;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAO1C"}
package/dist/server.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  getMaxSessionsLimit,
7
7
  hashMessage,
8
8
  startProxyServer
9
- } from "./cli-s6f9jefk.js";
9
+ } from "./cli-jk1p4nw8.js";
10
10
  import"./cli-rtab0qa6.js";
11
11
  import"./cli-m9pfb7h9.js";
12
12
  import"./cli-a05ws7rb.js";
package/package.json CHANGED
@@ -1,78 +1,78 @@
1
1
  {
2
- "name": "@rynfar/meridian",
3
- "version": "1.25.1",
4
- "description": "Local Anthropic API powered by your Claude Max subscription. One subscription, every agent.",
5
- "type": "module",
6
- "main": "./dist/server.js",
7
- "types": "./dist/proxy/server.d.ts",
8
- "bin": {
9
- "meridian": "./dist/cli.js",
10
- "claude-max-proxy": "./dist/cli.js"
11
- },
12
- "exports": {
13
- ".": {
14
- "types": "./dist/proxy/server.d.ts",
15
- "default": "./dist/server.js"
16
- }
17
- },
18
- "engines": {
19
- "node": ">=22"
20
- },
21
- "scripts": {
22
- "start": "./bin/claude-proxy-supervisor.sh",
23
- "proxy": "./bin/claude-proxy-supervisor.sh",
24
- "build": "rm -rf dist && bun build bin/cli.ts src/proxy/server.ts --outdir dist --target node --splitting --external @anthropic-ai/claude-agent-sdk --entry-naming '[name].js' && tsc -p tsconfig.build.json",
25
- "postbuild": "node --check dist/cli.js && node --check dist/server.js && test -f dist/proxy/server.d.ts",
26
- "prepublishOnly": "bun run build",
27
- "test": "bun test --path-ignore-patterns '**/*session-store*' --path-ignore-patterns '**/*proxy-async-ops*' --path-ignore-patterns '**/*proxy-extra-usage-fallback*' --path-ignore-patterns '**/*models-auth-status*' && bun test src/__tests__/proxy-extra-usage-fallback.test.ts && bun test src/__tests__/proxy-async-ops.test.ts && bun test src/__tests__/proxy-session-store.test.ts && bun test src/__tests__/session-store-pruning.test.ts && bun test src/__tests__/proxy-session-store-locking.test.ts && bun test src/__tests__/models-auth-status.test.ts",
28
- "typecheck": "tsc --noEmit",
29
- "proxy:direct": "bun run ./bin/cli.ts"
30
- },
31
- "dependencies": {
32
- "@anthropic-ai/claude-agent-sdk": "^0.2.80"
33
- },
34
- "devDependencies": {
35
- "@hono/node-server": "^1.19.11",
36
- "@types/bun": "^1.3.11",
37
- "@types/node": "^22.0.0",
38
- "glob": "^13.0.0",
39
- "hono": "^4.11.4",
40
- "typescript": "^5.8.2"
41
- },
42
- "files": [
43
- "dist/",
44
- "plugin/",
45
- "assets/",
46
- "README.md"
47
- ],
48
- "keywords": [
49
- "meridian",
50
- "claude",
51
- "claude-max",
52
- "claude-code",
53
- "anthropic",
54
- "anthropic-api",
55
- "claude-agent-sdk",
56
- "ai-coding",
57
- "ai-assistant",
58
- "proxy",
59
- "opencode",
60
- "cline",
61
- "aider",
62
- "llm",
63
- "coding-assistant",
64
- "opencode-claude-max-proxy"
65
- ],
66
- "repository": {
67
- "type": "git",
68
- "url": "git+https://github.com/rynfar/meridian.git"
69
- },
70
- "homepage": "https://github.com/rynfar/meridian#readme",
71
- "bugs": {
72
- "url": "https://github.com/rynfar/meridian/issues"
73
- },
74
- "author": "rynfar",
75
- "license": "MIT",
76
- "private": false,
77
- "packageManager": "bun@1.3.11"
2
+ "name": "@rynfar/meridian",
3
+ "version": "1.26.6",
4
+ "description": "Local Anthropic API powered by your Claude Max subscription. One subscription, every agent.",
5
+ "type": "module",
6
+ "main": "./dist/server.js",
7
+ "types": "./dist/proxy/server.d.ts",
8
+ "bin": {
9
+ "meridian": "./dist/cli.js",
10
+ "claude-max-proxy": "./dist/cli.js"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/proxy/server.d.ts",
15
+ "default": "./dist/server.js"
16
+ }
17
+ },
18
+ "engines": {
19
+ "node": ">=22"
20
+ },
21
+ "scripts": {
22
+ "start": "./bin/claude-proxy-supervisor.sh",
23
+ "proxy": "./bin/claude-proxy-supervisor.sh",
24
+ "build": "rm -rf dist && bun build bin/cli.ts src/proxy/server.ts --outdir dist --target node --splitting --external @anthropic-ai/claude-agent-sdk --entry-naming '[name].js' && tsc -p tsconfig.build.json",
25
+ "postbuild": "node --check dist/cli.js && node --check dist/server.js && test -f dist/proxy/server.d.ts",
26
+ "prepublishOnly": "bun run build",
27
+ "test": "bun test --path-ignore-patterns '**/*session-store*' --path-ignore-patterns '**/*proxy-async-ops*' --path-ignore-patterns '**/*proxy-extra-usage-fallback*' --path-ignore-patterns '**/*models-auth-status*' --path-ignore-patterns '**/*proxy-context-usage-store*' && bun test src/__tests__/proxy-extra-usage-fallback.test.ts && bun test src/__tests__/proxy-async-ops.test.ts && bun test src/__tests__/proxy-session-store.test.ts && bun test src/__tests__/session-store-pruning.test.ts && bun test src/__tests__/proxy-session-store-locking.test.ts && bun test src/__tests__/proxy-context-usage-store.test.ts && bun test src/__tests__/models-auth-status.test.ts",
28
+ "typecheck": "tsc --noEmit",
29
+ "proxy:direct": "bun run ./bin/cli.ts"
30
+ },
31
+ "dependencies": {
32
+ "@anthropic-ai/claude-agent-sdk": "^0.2.89"
33
+ },
34
+ "devDependencies": {
35
+ "@hono/node-server": "^1.19.11",
36
+ "@types/bun": "^1.3.11",
37
+ "@types/node": "^22.0.0",
38
+ "glob": "^13.0.0",
39
+ "hono": "^4.11.4",
40
+ "typescript": "^5.8.2"
41
+ },
42
+ "files": [
43
+ "dist/",
44
+ "plugin/",
45
+ "assets/",
46
+ "README.md"
47
+ ],
48
+ "keywords": [
49
+ "meridian",
50
+ "claude",
51
+ "claude-max",
52
+ "claude-code",
53
+ "anthropic",
54
+ "anthropic-api",
55
+ "claude-agent-sdk",
56
+ "ai-coding",
57
+ "ai-assistant",
58
+ "proxy",
59
+ "opencode",
60
+ "cline",
61
+ "aider",
62
+ "llm",
63
+ "coding-assistant",
64
+ "opencode-claude-max-proxy"
65
+ ],
66
+ "repository": {
67
+ "type": "git",
68
+ "url": "git+https://github.com/rynfar/meridian.git"
69
+ },
70
+ "homepage": "https://github.com/rynfar/meridian#readme",
71
+ "bugs": {
72
+ "url": "https://github.com/rynfar/meridian/issues"
73
+ },
74
+ "author": "rynfar",
75
+ "license": "MIT",
76
+ "private": false,
77
+ "packageManager": "bun@1.3.11"
78
78
  }