@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 +25 -2
- package/dist/{cli-s6f9jefk.js → cli-jk1p4nw8.js} +366 -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();
|
|
@@ -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
|
|
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
|
|
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
|
@@ -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,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;
|
|
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.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
|
}
|