@rynfar/meridian 1.25.1 → 1.26.5
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 +25 -2
- package/dist/{cli-s6f9jefk.js → cli-yve9q0a0.js} +365 -17
- package/dist/cli.js +1 -1
- package/dist/proxy/openai.d.ts +142 -0
- package/dist/proxy/openai.d.ts.map +1 -0
- package/dist/proxy/query.d.ts +23 -63
- package/dist/proxy/query.d.ts.map +1 -1
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/session/cache.d.ts +9 -4
- package/dist/proxy/session/cache.d.ts.map +1 -1
- package/dist/proxy/session/lineage.d.ts +9 -0
- package/dist/proxy/session/lineage.d.ts.map +1 -1
- package/dist/proxy/sessionStore.d.ts +5 -1
- package/dist/proxy/sessionStore.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +76 -76
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
|
-
| [
|
|
211
|
+
| [Open WebUI](https://github.com/open-webui/open-webui) | ✅ Verified | OpenAI-compatible endpoints — set 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();
|
|
@@ -13504,7 +13648,11 @@ function buildQueryOptions(ctx) {
|
|
|
13504
13648
|
...Object.keys(sdkAgents).length > 0 ? { agents: sdkAgents } : {},
|
|
13505
13649
|
...resumeSessionId ? { resume: resumeSessionId } : {},
|
|
13506
13650
|
...isUndo ? { forkSession: true, ...undoRollbackUuid ? { resumeSessionAt: undoRollbackUuid } : {} } : {},
|
|
13507
|
-
...sdkHooks ? { hooks: sdkHooks } : {}
|
|
13651
|
+
...sdkHooks ? { hooks: sdkHooks } : {},
|
|
13652
|
+
...effort ? { effort } : {},
|
|
13653
|
+
...thinking ? { thinking } : {},
|
|
13654
|
+
...taskBudget ? { taskBudget } : {},
|
|
13655
|
+
...betas && betas.length > 0 ? { betas } : {}
|
|
13508
13656
|
}
|
|
13509
13657
|
};
|
|
13510
13658
|
}
|
|
@@ -13784,7 +13932,19 @@ function lookupSharedSession(key) {
|
|
|
13784
13932
|
const store = readStore();
|
|
13785
13933
|
return store[key];
|
|
13786
13934
|
}
|
|
13787
|
-
function
|
|
13935
|
+
function lookupSharedSessionByClaudeId(claudeSessionId) {
|
|
13936
|
+
const sessions = Object.values(readStore());
|
|
13937
|
+
let newest;
|
|
13938
|
+
for (const session of sessions) {
|
|
13939
|
+
if (session.claudeSessionId !== claudeSessionId)
|
|
13940
|
+
continue;
|
|
13941
|
+
if (!newest || session.lastUsedAt > newest.lastUsedAt) {
|
|
13942
|
+
newest = session;
|
|
13943
|
+
}
|
|
13944
|
+
}
|
|
13945
|
+
return newest;
|
|
13946
|
+
}
|
|
13947
|
+
function storeSharedSession(key, claudeSessionId, messageCount, lineageHash, messageHashes, sdkMessageUuids, contextUsage) {
|
|
13788
13948
|
const path3 = getStorePath();
|
|
13789
13949
|
const lockPath = `${path3}.lock`;
|
|
13790
13950
|
const hasLock = skipLocking ? false : acquireLock(lockPath);
|
|
@@ -13801,7 +13961,8 @@ function storeSharedSession(key, claudeSessionId, messageCount, lineageHash, mes
|
|
|
13801
13961
|
messageCount: messageCount ?? existing?.messageCount ?? 0,
|
|
13802
13962
|
lineageHash: lineageHash ?? existing?.lineageHash,
|
|
13803
13963
|
messageHashes: messageHashes ?? existing?.messageHashes,
|
|
13804
|
-
sdkMessageUuids: sdkMessageUuids ?? existing?.sdkMessageUuids
|
|
13964
|
+
sdkMessageUuids: sdkMessageUuids ?? existing?.sdkMessageUuids,
|
|
13965
|
+
contextUsage: contextUsage ?? existing?.contextUsage
|
|
13805
13966
|
};
|
|
13806
13967
|
const maxEntries = getMaxStoredSessions();
|
|
13807
13968
|
const keys = Object.keys(store);
|
|
@@ -13947,7 +14108,8 @@ function lookupSession(sessionId, messages, workingDirectory) {
|
|
|
13947
14108
|
messageCount: shared.messageCount || 0,
|
|
13948
14109
|
lineageHash: shared.lineageHash || "",
|
|
13949
14110
|
messageHashes: shared.messageHashes,
|
|
13950
|
-
sdkMessageUuids: shared.sdkMessageUuids
|
|
14111
|
+
sdkMessageUuids: shared.sdkMessageUuids,
|
|
14112
|
+
contextUsage: shared.contextUsage
|
|
13951
14113
|
};
|
|
13952
14114
|
const result = verifyLineage(state, messages, sessionId, sessionCache);
|
|
13953
14115
|
if (result.type === "continuation" || result.type === "compaction") {
|
|
@@ -13974,7 +14136,8 @@ function lookupSession(sessionId, messages, workingDirectory) {
|
|
|
13974
14136
|
messageCount: shared.messageCount || 0,
|
|
13975
14137
|
lineageHash: shared.lineageHash || "",
|
|
13976
14138
|
messageHashes: shared.messageHashes,
|
|
13977
|
-
sdkMessageUuids: shared.sdkMessageUuids
|
|
14139
|
+
sdkMessageUuids: shared.sdkMessageUuids,
|
|
14140
|
+
contextUsage: shared.contextUsage
|
|
13978
14141
|
};
|
|
13979
14142
|
const result = verifyLineage(state, messages, fp, fingerprintCache);
|
|
13980
14143
|
if (result.type === "continuation" || result.type === "compaction") {
|
|
@@ -13985,7 +14148,34 @@ function lookupSession(sessionId, messages, workingDirectory) {
|
|
|
13985
14148
|
}
|
|
13986
14149
|
return { type: "diverged" };
|
|
13987
14150
|
}
|
|
13988
|
-
function
|
|
14151
|
+
function getSessionByClaudeId(claudeSessionId) {
|
|
14152
|
+
let newest;
|
|
14153
|
+
const consider = (state) => {
|
|
14154
|
+
if (!state || state.claudeSessionId !== claudeSessionId)
|
|
14155
|
+
return;
|
|
14156
|
+
if (!newest || state.lastAccess > newest.lastAccess) {
|
|
14157
|
+
newest = state;
|
|
14158
|
+
}
|
|
14159
|
+
};
|
|
14160
|
+
for (const state of sessionCache.values())
|
|
14161
|
+
consider(state);
|
|
14162
|
+
for (const state of fingerprintCache.values())
|
|
14163
|
+
consider(state);
|
|
14164
|
+
const shared = lookupSharedSessionByClaudeId(claudeSessionId);
|
|
14165
|
+
if (shared) {
|
|
14166
|
+
consider({
|
|
14167
|
+
claudeSessionId: shared.claudeSessionId,
|
|
14168
|
+
lastAccess: shared.lastUsedAt,
|
|
14169
|
+
messageCount: shared.messageCount || 0,
|
|
14170
|
+
lineageHash: shared.lineageHash || "",
|
|
14171
|
+
messageHashes: shared.messageHashes,
|
|
14172
|
+
sdkMessageUuids: shared.sdkMessageUuids,
|
|
14173
|
+
contextUsage: shared.contextUsage
|
|
14174
|
+
});
|
|
14175
|
+
}
|
|
14176
|
+
return newest;
|
|
14177
|
+
}
|
|
14178
|
+
function storeSession(sessionId, messages, claudeSessionId, workingDirectory, sdkMessageUuids, contextUsage) {
|
|
13989
14179
|
if (!claudeSessionId)
|
|
13990
14180
|
return;
|
|
13991
14181
|
const lineageHash = computeLineageHash(messages);
|
|
@@ -13996,7 +14186,8 @@ function storeSession(sessionId, messages, claudeSessionId, workingDirectory, sd
|
|
|
13996
14186
|
messageCount: messages?.length || 0,
|
|
13997
14187
|
lineageHash,
|
|
13998
14188
|
messageHashes,
|
|
13999
|
-
sdkMessageUuids
|
|
14189
|
+
sdkMessageUuids,
|
|
14190
|
+
...contextUsage ? { contextUsage } : {}
|
|
14000
14191
|
};
|
|
14001
14192
|
if (sessionId)
|
|
14002
14193
|
sessionCache.set(sessionId, state);
|
|
@@ -14004,8 +14195,9 @@ function storeSession(sessionId, messages, claudeSessionId, workingDirectory, sd
|
|
|
14004
14195
|
if (fp)
|
|
14005
14196
|
fingerprintCache.set(fp, state);
|
|
14006
14197
|
const key = sessionId || fp;
|
|
14007
|
-
if (key)
|
|
14008
|
-
storeSharedSession(key, claudeSessionId, state.messageCount, lineageHash, messageHashes, sdkMessageUuids);
|
|
14198
|
+
if (key) {
|
|
14199
|
+
storeSharedSession(key, claudeSessionId, state.messageCount, lineageHash, messageHashes, sdkMessageUuids, contextUsage);
|
|
14200
|
+
}
|
|
14009
14201
|
}
|
|
14010
14202
|
|
|
14011
14203
|
// src/proxy/server.ts
|
|
@@ -14083,6 +14275,16 @@ function buildFreshPrompt(messages, stripCacheControl) {
|
|
|
14083
14275
|
|
|
14084
14276
|
`) || "";
|
|
14085
14277
|
}
|
|
14278
|
+
function logUsage(requestId, usage) {
|
|
14279
|
+
const fmt = (n) => n > 1000 ? `${Math.round(n / 1000)}k` : String(n);
|
|
14280
|
+
const parts = [
|
|
14281
|
+
`input=${fmt(usage.input_tokens ?? 0)}`,
|
|
14282
|
+
`output=${fmt(usage.output_tokens ?? 0)}`,
|
|
14283
|
+
...usage.cache_read_input_tokens ? [`cache_read=${fmt(usage.cache_read_input_tokens)}`] : [],
|
|
14284
|
+
...usage.cache_creation_input_tokens ? [`cache_write=${fmt(usage.cache_creation_input_tokens)}`] : []
|
|
14285
|
+
];
|
|
14286
|
+
console.error(`[PROXY] ${requestId} usage: ${parts.join(" ")}`);
|
|
14287
|
+
}
|
|
14086
14288
|
function createProxyServer(config = {}) {
|
|
14087
14289
|
const finalConfig = { ...DEFAULT_PROXY_CONFIG, ...config };
|
|
14088
14290
|
const app = new Hono2;
|
|
@@ -14094,7 +14296,7 @@ function createProxyServer(config = {}) {
|
|
|
14094
14296
|
status: "ok",
|
|
14095
14297
|
service: "meridian",
|
|
14096
14298
|
format: "anthropic",
|
|
14097
|
-
endpoints: ["/v1/messages", "/messages", "/telemetry", "/health"]
|
|
14299
|
+
endpoints: ["/v1/messages", "/messages", "/v1/chat/completions", "/v1/models", "/telemetry", "/health"]
|
|
14098
14300
|
});
|
|
14099
14301
|
}
|
|
14100
14302
|
return c.html(landingHtml);
|
|
@@ -14170,6 +14372,22 @@ function createProxyServer(config = {}) {
|
|
|
14170
14372
|
`);
|
|
14171
14373
|
}
|
|
14172
14374
|
}
|
|
14375
|
+
const effortHeader = c.req.header("x-opencode-effort");
|
|
14376
|
+
const thinkingHeader = c.req.header("x-opencode-thinking");
|
|
14377
|
+
const taskBudgetHeader = c.req.header("x-opencode-task-budget");
|
|
14378
|
+
const betaHeader = c.req.header("anthropic-beta");
|
|
14379
|
+
const effort = effortHeader || body.effort || undefined;
|
|
14380
|
+
let thinking = body.thinking || undefined;
|
|
14381
|
+
if (thinkingHeader !== undefined) {
|
|
14382
|
+
try {
|
|
14383
|
+
thinking = JSON.parse(thinkingHeader);
|
|
14384
|
+
} catch (e) {
|
|
14385
|
+
console.error(`[PROXY] ${requestMeta.requestId} ignoring malformed x-opencode-thinking header: ${e instanceof Error ? e.message : String(e)}`);
|
|
14386
|
+
}
|
|
14387
|
+
}
|
|
14388
|
+
const parsedBudget = taskBudgetHeader ? Number.parseInt(taskBudgetHeader, 10) : NaN;
|
|
14389
|
+
const taskBudget = Number.isFinite(parsedBudget) ? { total: parsedBudget } : body.task_budget ? { total: body.task_budget.total ?? body.task_budget } : undefined;
|
|
14390
|
+
const betas = betaHeader ? betaHeader.split(",").map((b) => b.trim()).filter(Boolean) : undefined;
|
|
14173
14391
|
const agentSessionId = adapter.getSessionId(c);
|
|
14174
14392
|
const lineageResult = lookupSession(agentSessionId, body.messages || [], workingDirectory);
|
|
14175
14393
|
const isResume = lineageResult.type === "continuation" || lineageResult.type === "compaction";
|
|
@@ -14343,6 +14561,7 @@ function createProxyServer(config = {}) {
|
|
|
14343
14561
|
while (sdkUuidMap.length < allMessages.length)
|
|
14344
14562
|
sdkUuidMap.push(null);
|
|
14345
14563
|
claudeLog("upstream.start", { mode: "non_stream", model });
|
|
14564
|
+
let lastUsage;
|
|
14346
14565
|
try {
|
|
14347
14566
|
if (!claudeExecutable) {
|
|
14348
14567
|
claudeExecutable = await resolveClaudeExecutableAsync();
|
|
@@ -14371,7 +14590,11 @@ function createProxyServer(config = {}) {
|
|
|
14371
14590
|
undoRollbackUuid,
|
|
14372
14591
|
sdkHooks,
|
|
14373
14592
|
adapter,
|
|
14374
|
-
onStderr
|
|
14593
|
+
onStderr,
|
|
14594
|
+
effort,
|
|
14595
|
+
thinking,
|
|
14596
|
+
taskBudget,
|
|
14597
|
+
betas
|
|
14375
14598
|
}))) {
|
|
14376
14599
|
if (event.type === "assistant" && !event.error) {
|
|
14377
14600
|
didYieldContent = true;
|
|
@@ -14410,7 +14633,11 @@ function createProxyServer(config = {}) {
|
|
|
14410
14633
|
undoRollbackUuid: undefined,
|
|
14411
14634
|
sdkHooks,
|
|
14412
14635
|
adapter,
|
|
14413
|
-
onStderr
|
|
14636
|
+
onStderr,
|
|
14637
|
+
effort,
|
|
14638
|
+
thinking,
|
|
14639
|
+
taskBudget,
|
|
14640
|
+
betas
|
|
14414
14641
|
}));
|
|
14415
14642
|
return;
|
|
14416
14643
|
}
|
|
@@ -14492,6 +14719,9 @@ function createProxyServer(config = {}) {
|
|
|
14492
14719
|
}
|
|
14493
14720
|
contentBlocks.push(b);
|
|
14494
14721
|
}
|
|
14722
|
+
const msgUsage = message.message.usage;
|
|
14723
|
+
if (msgUsage)
|
|
14724
|
+
lastUsage = { ...lastUsage, ...msgUsage };
|
|
14495
14725
|
}
|
|
14496
14726
|
}
|
|
14497
14727
|
claudeLog("upstream.completed", {
|
|
@@ -14500,6 +14730,8 @@ function createProxyServer(config = {}) {
|
|
|
14500
14730
|
assistantMessages,
|
|
14501
14731
|
durationMs: Date.now() - upstreamStartAt
|
|
14502
14732
|
});
|
|
14733
|
+
if (lastUsage)
|
|
14734
|
+
logUsage(requestMeta.requestId, lastUsage);
|
|
14503
14735
|
} catch (error) {
|
|
14504
14736
|
const stderrOutput = stderrLines.join(`
|
|
14505
14737
|
`).trim();
|
|
@@ -14584,7 +14816,7 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
14584
14816
|
error: null
|
|
14585
14817
|
});
|
|
14586
14818
|
if (currentSessionId) {
|
|
14587
|
-
storeSession(agentSessionId, body.messages || [], currentSessionId, workingDirectory, sdkUuidMap);
|
|
14819
|
+
storeSession(agentSessionId, body.messages || [], currentSessionId, workingDirectory, sdkUuidMap, lastUsage);
|
|
14588
14820
|
}
|
|
14589
14821
|
const responseSessionId = currentSessionId || resumeSessionId || `session_${Date.now()}`;
|
|
14590
14822
|
return new Response(JSON.stringify({
|
|
@@ -14638,6 +14870,7 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
14638
14870
|
while (sdkUuidMap.length < allMessages.length)
|
|
14639
14871
|
sdkUuidMap.push(null);
|
|
14640
14872
|
let messageStartEmitted = false;
|
|
14873
|
+
let lastUsage;
|
|
14641
14874
|
try {
|
|
14642
14875
|
let currentSessionId;
|
|
14643
14876
|
const MAX_RATE_LIMIT_RETRIES = 2;
|
|
@@ -14664,7 +14897,11 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
14664
14897
|
undoRollbackUuid,
|
|
14665
14898
|
sdkHooks,
|
|
14666
14899
|
adapter,
|
|
14667
|
-
onStderr
|
|
14900
|
+
onStderr,
|
|
14901
|
+
effort,
|
|
14902
|
+
thinking,
|
|
14903
|
+
taskBudget,
|
|
14904
|
+
betas
|
|
14668
14905
|
}))) {
|
|
14669
14906
|
if (event.type === "stream_event") {
|
|
14670
14907
|
didYieldClientEvent = true;
|
|
@@ -14703,7 +14940,11 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
14703
14940
|
undoRollbackUuid: undefined,
|
|
14704
14941
|
sdkHooks,
|
|
14705
14942
|
adapter,
|
|
14706
|
-
onStderr
|
|
14943
|
+
onStderr,
|
|
14944
|
+
effort,
|
|
14945
|
+
thinking,
|
|
14946
|
+
taskBudget,
|
|
14947
|
+
betas
|
|
14707
14948
|
}));
|
|
14708
14949
|
return;
|
|
14709
14950
|
}
|
|
@@ -14813,6 +15054,9 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
14813
15054
|
if (eventType === "message_start") {
|
|
14814
15055
|
skipBlockIndices.clear();
|
|
14815
15056
|
sdkToClientIndex.clear();
|
|
15057
|
+
const startUsage = event.message?.usage;
|
|
15058
|
+
if (startUsage)
|
|
15059
|
+
lastUsage = { ...lastUsage, ...startUsage };
|
|
14816
15060
|
if (messageStartEmitted) {
|
|
14817
15061
|
continue;
|
|
14818
15062
|
}
|
|
@@ -14845,6 +15089,9 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
14845
15089
|
event.index = sdkToClientIndex.get(eventIndex);
|
|
14846
15090
|
}
|
|
14847
15091
|
if (eventType === "message_delta") {
|
|
15092
|
+
const deltaUsage = event.usage;
|
|
15093
|
+
if (deltaUsage)
|
|
15094
|
+
lastUsage = { ...lastUsage, ...deltaUsage };
|
|
14848
15095
|
const stopReason = event.delta?.stop_reason;
|
|
14849
15096
|
if (stopReason === "tool_use" && skipBlockIndices.size > 0) {
|
|
14850
15097
|
continue;
|
|
@@ -14886,8 +15133,10 @@ data: ${JSON.stringify({ type: "message_stop" })}
|
|
|
14886
15133
|
eventsForwarded,
|
|
14887
15134
|
textEventsForwarded
|
|
14888
15135
|
});
|
|
15136
|
+
if (lastUsage)
|
|
15137
|
+
logUsage(requestMeta.requestId, lastUsage);
|
|
14889
15138
|
if (currentSessionId) {
|
|
14890
|
-
storeSession(agentSessionId, body.messages || [], currentSessionId, workingDirectory, sdkUuidMap);
|
|
15139
|
+
storeSession(agentSessionId, body.messages || [], currentSessionId, workingDirectory, sdkUuidMap, lastUsage);
|
|
14891
15140
|
}
|
|
14892
15141
|
if (!streamClosed) {
|
|
14893
15142
|
const unseenToolUses = capturedToolUses.filter((tu) => !streamedToolUseIds.has(tu.id));
|
|
@@ -15180,6 +15429,105 @@ data: ${JSON.stringify({
|
|
|
15180
15429
|
}
|
|
15181
15430
|
return c.json({ success: false, message: "Token refresh failed. If the problem persists, run 'claude login'." }, 500);
|
|
15182
15431
|
});
|
|
15432
|
+
app.post("/v1/chat/completions", async (c) => {
|
|
15433
|
+
const rawBody = await c.req.json();
|
|
15434
|
+
const anthropicBody = translateOpenAiToAnthropic(rawBody);
|
|
15435
|
+
if (!anthropicBody) {
|
|
15436
|
+
return c.json({ type: "error", error: { type: "invalid_request_error", message: "messages: Field required" } }, 400);
|
|
15437
|
+
}
|
|
15438
|
+
const internalReq = new Request("http://internal/v1/messages", {
|
|
15439
|
+
method: "POST",
|
|
15440
|
+
headers: { "Content-Type": "application/json" },
|
|
15441
|
+
body: JSON.stringify(anthropicBody)
|
|
15442
|
+
});
|
|
15443
|
+
const internalRes = await app.fetch(internalReq);
|
|
15444
|
+
if (!internalRes.ok) {
|
|
15445
|
+
const errBody = await internalRes.text();
|
|
15446
|
+
return c.json({ type: "error", error: { type: "upstream_error", message: errBody } }, internalRes.status);
|
|
15447
|
+
}
|
|
15448
|
+
const completionId = `chatcmpl-${randomUUID()}`;
|
|
15449
|
+
const created = Math.floor(Date.now() / 1000);
|
|
15450
|
+
const model = typeof rawBody.model === "string" && rawBody.model ? rawBody.model : "claude-sonnet-4-6";
|
|
15451
|
+
if (!anthropicBody.stream) {
|
|
15452
|
+
const anthropicRes = await internalRes.json();
|
|
15453
|
+
return c.json(translateAnthropicToOpenAi(anthropicRes, completionId, model, created));
|
|
15454
|
+
}
|
|
15455
|
+
const encoder = new TextEncoder;
|
|
15456
|
+
const readable = new ReadableStream({
|
|
15457
|
+
async start(controller) {
|
|
15458
|
+
const reader = internalRes.body?.getReader();
|
|
15459
|
+
if (!reader) {
|
|
15460
|
+
controller.close();
|
|
15461
|
+
return;
|
|
15462
|
+
}
|
|
15463
|
+
const decoder = new TextDecoder;
|
|
15464
|
+
let buffer = "";
|
|
15465
|
+
let streamError = null;
|
|
15466
|
+
try {
|
|
15467
|
+
while (true) {
|
|
15468
|
+
const { done, value } = await reader.read();
|
|
15469
|
+
if (done)
|
|
15470
|
+
break;
|
|
15471
|
+
buffer += decoder.decode(value, { stream: true });
|
|
15472
|
+
const lines = buffer.split(`
|
|
15473
|
+
`);
|
|
15474
|
+
buffer = lines.pop() ?? "";
|
|
15475
|
+
for (const line of lines) {
|
|
15476
|
+
if (!line.startsWith("data: "))
|
|
15477
|
+
continue;
|
|
15478
|
+
const dataStr = line.slice(6).trim();
|
|
15479
|
+
if (!dataStr)
|
|
15480
|
+
continue;
|
|
15481
|
+
let event;
|
|
15482
|
+
try {
|
|
15483
|
+
event = JSON.parse(dataStr);
|
|
15484
|
+
} catch {
|
|
15485
|
+
continue;
|
|
15486
|
+
}
|
|
15487
|
+
if (typeof event.type !== "string")
|
|
15488
|
+
continue;
|
|
15489
|
+
const chunk = translateAnthropicSseEvent(event, completionId, model, created);
|
|
15490
|
+
if (chunk)
|
|
15491
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}
|
|
15492
|
+
|
|
15493
|
+
`));
|
|
15494
|
+
}
|
|
15495
|
+
}
|
|
15496
|
+
} catch (err) {
|
|
15497
|
+
streamError = err instanceof Error ? err : new Error(String(err));
|
|
15498
|
+
} finally {
|
|
15499
|
+
if (!streamError)
|
|
15500
|
+
controller.enqueue(encoder.encode(`data: [DONE]
|
|
15501
|
+
|
|
15502
|
+
`));
|
|
15503
|
+
controller.close();
|
|
15504
|
+
}
|
|
15505
|
+
}
|
|
15506
|
+
});
|
|
15507
|
+
return new Response(readable, {
|
|
15508
|
+
headers: {
|
|
15509
|
+
"Content-Type": "text/event-stream",
|
|
15510
|
+
"Cache-Control": "no-cache",
|
|
15511
|
+
Connection: "keep-alive"
|
|
15512
|
+
}
|
|
15513
|
+
});
|
|
15514
|
+
});
|
|
15515
|
+
app.get("/v1/models", async (c) => {
|
|
15516
|
+
const authStatus = await getClaudeAuthStatusAsync();
|
|
15517
|
+
const isMax = authStatus?.subscriptionType === "max";
|
|
15518
|
+
return c.json({ object: "list", data: buildModelList(isMax) });
|
|
15519
|
+
});
|
|
15520
|
+
app.get("/v1/sessions/:claudeSessionId/context-usage", (c) => {
|
|
15521
|
+
const claudeSessionId = c.req.param("claudeSessionId");
|
|
15522
|
+
const session = getSessionByClaudeId(claudeSessionId);
|
|
15523
|
+
if (!session) {
|
|
15524
|
+
return c.json({ error: "Session not found" }, 404);
|
|
15525
|
+
}
|
|
15526
|
+
if (!session.contextUsage) {
|
|
15527
|
+
return c.json({ error: "No usage data available for this session" }, 404);
|
|
15528
|
+
}
|
|
15529
|
+
return c.json({ session_id: claudeSessionId, context_usage: session.contextUsage });
|
|
15530
|
+
});
|
|
15183
15531
|
app.all("*", (c) => {
|
|
15184
15532
|
console.error(`[PROXY] UNHANDLED ${c.req.method} ${c.req.url}`);
|
|
15185
15533
|
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
|
@@ -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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"}
|
package/dist/proxy/query.d.ts
CHANGED
|
@@ -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
|
|
49
|
-
prompt:
|
|
50
|
-
options:
|
|
51
|
-
|
|
52
|
-
|
|
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;
|
|
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,CA8DrE"}
|
|
@@ -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;
|
|
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:
|
|
31
|
-
}>, claudeSessionId: string, workingDirectory?: string, sdkMessageUuids?: Array<string | null
|
|
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;
|
|
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;
|
|
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
|
|
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;
|
|
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
package/package.json
CHANGED
|
@@ -1,78 +1,78 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
2
|
+
"name": "@rynfar/meridian",
|
|
3
|
+
"version": "1.26.5",
|
|
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
|
}
|