@sifwenf/cc-proxy 0.1.0 → 0.1.1
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/bun.lock +31 -0
- package/package.json +2 -2
- package/src/server.ts +35 -9
package/bun.lock
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "cc-proxy",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"chokidar": "^5.0.0",
|
|
9
|
+
"hono": "^4.0.0",
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@types/bun": "^1.0.0",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
"packages": {
|
|
17
|
+
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
|
18
|
+
|
|
19
|
+
"@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="],
|
|
20
|
+
|
|
21
|
+
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
|
22
|
+
|
|
23
|
+
"chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
|
24
|
+
|
|
25
|
+
"hono": ["hono@4.11.9", "", {}, "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ=="],
|
|
26
|
+
|
|
27
|
+
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
|
28
|
+
|
|
29
|
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
|
30
|
+
}
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sifwenf/cc-proxy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Lightweight LLM proxy server for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"ccp": "
|
|
7
|
+
"ccp": "scripts/ccp"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "bun run src/server.ts",
|
package/src/server.ts
CHANGED
|
@@ -11,6 +11,29 @@ import chokidar from 'chokidar';
|
|
|
11
11
|
|
|
12
12
|
const app = new Hono();
|
|
13
13
|
|
|
14
|
+
// Strip thinking blocks from messages to avoid signature mismatch errors
|
|
15
|
+
// when switching between different Claude models
|
|
16
|
+
function stripThinkingBlocks(body: any): any {
|
|
17
|
+
if (!body.messages || !Array.isArray(body.messages)) {
|
|
18
|
+
return body;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const processed = {
|
|
22
|
+
...body,
|
|
23
|
+
messages: body.messages.map((msg: any) => {
|
|
24
|
+
if (!msg.content || !Array.isArray(msg.content)) {
|
|
25
|
+
return msg;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
...msg,
|
|
29
|
+
content: msg.content.filter((item: any) => item.type !== 'thinking')
|
|
30
|
+
};
|
|
31
|
+
})
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return processed;
|
|
35
|
+
}
|
|
36
|
+
|
|
14
37
|
let config;
|
|
15
38
|
try {
|
|
16
39
|
config = loadConfig();
|
|
@@ -120,16 +143,19 @@ app.post('/v1/messages', async (c) => {
|
|
|
120
143
|
const body = await c.req.json();
|
|
121
144
|
const headers = Object.fromEntries(c.req.raw.headers);
|
|
122
145
|
|
|
146
|
+
// Strip thinking blocks to avoid signature mismatch when switching models
|
|
147
|
+
const processedBody = stripThinkingBlocks(body);
|
|
148
|
+
|
|
123
149
|
// Log request
|
|
124
150
|
requestId = appLogger.startRequest(
|
|
125
151
|
'POST',
|
|
126
152
|
'/v1/messages',
|
|
127
153
|
headers,
|
|
128
|
-
|
|
154
|
+
processedBody
|
|
129
155
|
);
|
|
130
156
|
|
|
131
157
|
// Resolve provider and model mapping
|
|
132
|
-
const context = requestMapper.resolveProvider(
|
|
158
|
+
const context = requestMapper.resolveProvider(processedBody.model);
|
|
133
159
|
const { provider, modelName } = context;
|
|
134
160
|
|
|
135
161
|
// Normalize format to lowercase
|
|
@@ -141,7 +167,7 @@ app.post('/v1/messages', async (c) => {
|
|
|
141
167
|
|
|
142
168
|
if (format === 'openai') {
|
|
143
169
|
// Convert Anthropic → OpenAI format
|
|
144
|
-
providerRequest = convertAnthropicToOpenAI({ ...
|
|
170
|
+
providerRequest = convertAnthropicToOpenAI({ ...processedBody, model: modelName });
|
|
145
171
|
fetchHeaders = {
|
|
146
172
|
'Content-Type': 'application/json',
|
|
147
173
|
'Authorization': `Bearer ${provider.apiKey}`,
|
|
@@ -150,7 +176,7 @@ app.post('/v1/messages', async (c) => {
|
|
|
150
176
|
};
|
|
151
177
|
} else if (format === 'anthropic') {
|
|
152
178
|
// Explicit Anthropic format headers
|
|
153
|
-
providerRequest = { ...
|
|
179
|
+
providerRequest = { ...processedBody, model: modelName };
|
|
154
180
|
fetchHeaders = {
|
|
155
181
|
'Content-Type': 'application/json',
|
|
156
182
|
'x-api-key': provider.apiKey,
|
|
@@ -169,12 +195,12 @@ app.post('/v1/messages', async (c) => {
|
|
|
169
195
|
if (provider.apiKey) {
|
|
170
196
|
fetchHeaders['x-api-key'] = provider.apiKey;
|
|
171
197
|
}
|
|
172
|
-
providerRequest = { ...
|
|
198
|
+
providerRequest = { ...processedBody, model: modelName };
|
|
173
199
|
}
|
|
174
200
|
|
|
175
201
|
// Forward request to provider
|
|
176
202
|
// Log forward details before sending (only once, not verbose for streaming)
|
|
177
|
-
if (!
|
|
203
|
+
if (!processedBody.stream) {
|
|
178
204
|
appLogger.logForward(
|
|
179
205
|
provider.name,
|
|
180
206
|
modelName,
|
|
@@ -208,7 +234,7 @@ app.post('/v1/messages', async (c) => {
|
|
|
208
234
|
}
|
|
209
235
|
|
|
210
236
|
// Handle streaming response
|
|
211
|
-
if (
|
|
237
|
+
if (processedBody.stream) {
|
|
212
238
|
// Performance optimization: Skip per-chunk logging for streaming
|
|
213
239
|
// Each SSE chunk would trigger disk I/O, killing performance
|
|
214
240
|
// Instead, log a single summary after stream completes
|
|
@@ -254,7 +280,7 @@ app.post('/v1/messages', async (c) => {
|
|
|
254
280
|
|
|
255
281
|
// Convert OpenAI format to Anthropic format if needed
|
|
256
282
|
if (format === 'openai' && responseBody.choices) {
|
|
257
|
-
responseBody = convertOpenAIToAnthropic(responseBody,
|
|
283
|
+
responseBody = convertOpenAIToAnthropic(responseBody, processedBody.model);
|
|
258
284
|
}
|
|
259
285
|
|
|
260
286
|
// Log response details (combine forward + response into one call)
|
|
@@ -264,7 +290,7 @@ app.post('/v1/messages', async (c) => {
|
|
|
264
290
|
responseBody,
|
|
265
291
|
provider.name,
|
|
266
292
|
modelName,
|
|
267
|
-
|
|
293
|
+
processedBody.model
|
|
268
294
|
);
|
|
269
295
|
|
|
270
296
|
return c.json(responseBody);
|