@kylindc/ccxray 1.2.0 → 1.2.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/.cursor/commands/opsx-apply.md +152 -0
- package/.cursor/commands/opsx-archive.md +157 -0
- package/.cursor/commands/opsx-continue.md +114 -0
- package/.cursor/commands/opsx-explore.md +173 -0
- package/.cursor/commands/opsx-propose.md +106 -0
- package/.cursor/commands/opsx-sync.md +134 -0
- package/.cursor/skills/openspec-apply-change/SKILL.md +156 -0
- package/.cursor/skills/openspec-archive-change/SKILL.md +114 -0
- package/.cursor/skills/openspec-continue-change/SKILL.md +118 -0
- package/.cursor/skills/openspec-explore/SKILL.md +288 -0
- package/.cursor/skills/openspec-propose/SKILL.md +110 -0
- package/.cursor/skills/openspec-sync-specs/SKILL.md +138 -0
- package/.idea/ccxray.iml +9 -0
- package/.idea/go.imports.xml +11 -0
- package/.idea/misc.xml +7 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/package.json +1 -1
- package/server/config.js +2 -2
- package/server/forward.js +68 -3
- package/server/index.js +18 -3
package/server/forward.js
CHANGED
|
@@ -49,6 +49,26 @@ function forwardBedrockRequest(ctx) {
|
|
|
49
49
|
}
|
|
50
50
|
clientRes.writeHead(400, { 'Content-Type': 'application/json' });
|
|
51
51
|
clientRes.end(JSON.stringify({ error: 'bedrock_model_unknown', message: err.message }));
|
|
52
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
53
|
+
const errEvent = [{ type: 'error', error: 'bedrock_model_unknown', message: err.message }];
|
|
54
|
+
const resWritePromise = config.storage.write(id, '_res.json', JSON.stringify(errEvent))
|
|
55
|
+
.catch(e => console.error('Write res.json failed:', e.message));
|
|
56
|
+
const entry = {
|
|
57
|
+
id, ts, sessionId: reqSessionId, method: clientReq.method, url: clientReq.url,
|
|
58
|
+
req: parsedBody, res: errEvent, elapsed, status: 400, isSSE: false,
|
|
59
|
+
tokens: helpers.tokenizeRequest(parsedBody),
|
|
60
|
+
usage: null, cost: null, maxContext: null,
|
|
61
|
+
cwd: store.sessionMeta[reqSessionId]?.cwd || null,
|
|
62
|
+
receivedAt: startTime, model: parsedBody?.model || null,
|
|
63
|
+
msgCount: parsedBody?.messages?.length || 0, toolCount: parsedBody?.tools?.length || 0,
|
|
64
|
+
stopReason: 'error', title: null,
|
|
65
|
+
sysHash: ctx.sysHash || null, toolsHash: ctx.toolsHash || null,
|
|
66
|
+
};
|
|
67
|
+
entry._writePromise = Promise.all([ctx.reqWritePromise, resWritePromise].filter(Boolean));
|
|
68
|
+
store.entries.push(entry);
|
|
69
|
+
store.trimEntries();
|
|
70
|
+
broadcast(entry);
|
|
71
|
+
console.error(`\x1b[31m❌ BEDROCK MODEL UNKNOWN: ${err.message}\x1b[0m`);
|
|
52
72
|
return;
|
|
53
73
|
}
|
|
54
74
|
|
|
@@ -67,8 +87,8 @@ function forwardBedrockRequest(ctx) {
|
|
|
67
87
|
fwdHeaders['host'] = url.hostname;
|
|
68
88
|
|
|
69
89
|
// Auth: bearer token takes precedence over SigV4
|
|
70
|
-
if (config.
|
|
71
|
-
fwdHeaders['authorization'] = `Bearer ${config.
|
|
90
|
+
if (config.AWS_BEARER_TOKEN_BEDROCK) {
|
|
91
|
+
fwdHeaders['authorization'] = `Bearer ${config.AWS_BEARER_TOKEN_BEDROCK}`;
|
|
72
92
|
} else {
|
|
73
93
|
const creds = config.BEDROCK_CREDENTIALS;
|
|
74
94
|
const signed = sigv4.sign('POST', bedrockUrl, fwdHeaders, bodyToSend, creds, config.BEDROCK_RESOLVED_REGION, 'bedrock');
|
|
@@ -109,6 +129,26 @@ function forwardBedrockRequest(ctx) {
|
|
|
109
129
|
clientRes.writeHead(502, { 'Content-Type': 'application/json' });
|
|
110
130
|
}
|
|
111
131
|
clientRes.end(JSON.stringify({ error: 'proxy_error', message: err.message }));
|
|
132
|
+
|
|
133
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
134
|
+
const errEvent = [{ type: 'error', error: 'proxy_error', message: err.message }];
|
|
135
|
+
const resWritePromise = config.storage.write(id, '_res.json', JSON.stringify(errEvent))
|
|
136
|
+
.catch(e => console.error('Write res.json failed:', e.message));
|
|
137
|
+
const entry = {
|
|
138
|
+
id, ts, sessionId: reqSessionId, method: clientReq.method, url: clientReq.url,
|
|
139
|
+
req: parsedBody, res: errEvent, elapsed, status: 502, isSSE: false,
|
|
140
|
+
tokens: helpers.tokenizeRequest(parsedBody),
|
|
141
|
+
usage: null, cost: null, maxContext: null,
|
|
142
|
+
cwd: store.sessionMeta[reqSessionId]?.cwd || null,
|
|
143
|
+
receivedAt: startTime, model: parsedBody?.model || null,
|
|
144
|
+
msgCount: parsedBody?.messages?.length || 0, toolCount: parsedBody?.tools?.length || 0,
|
|
145
|
+
stopReason: 'error', title: null,
|
|
146
|
+
sysHash: ctx.sysHash || null, toolsHash: ctx.toolsHash || null,
|
|
147
|
+
};
|
|
148
|
+
entry._writePromise = Promise.all([ctx.reqWritePromise, resWritePromise].filter(Boolean));
|
|
149
|
+
store.entries.push(entry);
|
|
150
|
+
store.trimEntries();
|
|
151
|
+
broadcast(entry);
|
|
112
152
|
});
|
|
113
153
|
|
|
114
154
|
proxyReq.end(bodyToSend);
|
|
@@ -166,7 +206,32 @@ function handleBedrockSSEResponse(ctx, proxyRes, clientRes, bedrockModelId) {
|
|
|
166
206
|
});
|
|
167
207
|
|
|
168
208
|
proxyRes.on('end', () => {
|
|
169
|
-
if (streamErrored)
|
|
209
|
+
if (streamErrored) {
|
|
210
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
211
|
+
const errEvent = [{ type: 'error', error: 'modelStreamErrorException' }];
|
|
212
|
+
const resWritePromise = config.storage.write(id, '_res.json', JSON.stringify(errEvent))
|
|
213
|
+
.catch(e => console.error('Write res.json failed:', e.message));
|
|
214
|
+
if (reqSessionId) {
|
|
215
|
+
store.activeRequests[reqSessionId] = Math.max(0, (store.activeRequests[reqSessionId] || 1) - 1);
|
|
216
|
+
broadcastSessionStatus(reqSessionId);
|
|
217
|
+
}
|
|
218
|
+
const entry = {
|
|
219
|
+
id, ts: ctx.ts, sessionId: reqSessionId, method: ctx.clientReq.method, url: ctx.clientReq.url,
|
|
220
|
+
req: parsedBody, res: errEvent, elapsed, status: 500, isSSE: true,
|
|
221
|
+
tokens: helpers.tokenizeRequest(parsedBody),
|
|
222
|
+
usage: null, cost: null, maxContext: null,
|
|
223
|
+
cwd: store.sessionMeta[reqSessionId]?.cwd || null,
|
|
224
|
+
receivedAt: startTime, model: parsedBody?.model || null,
|
|
225
|
+
msgCount: parsedBody?.messages?.length || 0, toolCount: parsedBody?.tools?.length || 0,
|
|
226
|
+
stopReason: 'error', title: null,
|
|
227
|
+
sysHash: ctx.sysHash || null, toolsHash: ctx.toolsHash || null,
|
|
228
|
+
};
|
|
229
|
+
entry._writePromise = Promise.all([ctx.reqWritePromise, resWritePromise].filter(Boolean));
|
|
230
|
+
store.entries.push(entry);
|
|
231
|
+
store.trimEntries();
|
|
232
|
+
broadcast(entry);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
170
235
|
|
|
171
236
|
if (ctx.skipEntry) {
|
|
172
237
|
for (const held of heldEventStrs) clientRes.write(held);
|
package/server/index.js
CHANGED
|
@@ -291,6 +291,12 @@ function spawnClaude(port, args) {
|
|
|
291
291
|
delete childEnv[key];
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
|
+
// Bedrock users typically have no ANTHROPIC_API_KEY. Without it, Claude Code
|
|
295
|
+
// refuses to start ("Not logged in"). Set a placeholder — ccxray strips the
|
|
296
|
+
// x-api-key header before forwarding to Bedrock, so it never reaches AWS.
|
|
297
|
+
if (!childEnv.ANTHROPIC_API_KEY) {
|
|
298
|
+
childEnv.ANTHROPIC_API_KEY = 'bedrock-via-ccxray';
|
|
299
|
+
}
|
|
294
300
|
}
|
|
295
301
|
const child = spawn('claude', args, {
|
|
296
302
|
stdio: 'inherit',
|
|
@@ -405,9 +411,18 @@ async function startClientMode(lock) {
|
|
|
405
411
|
|
|
406
412
|
// Spawn claude pointing to hub
|
|
407
413
|
const { spawn } = require('child_process');
|
|
414
|
+
let hubClientEnv = { ...process.env, ANTHROPIC_BASE_URL: `http://localhost:${lock.port}` };
|
|
415
|
+
if (config.IS_BEDROCK_MODE) {
|
|
416
|
+
for (const key of Object.keys(hubClientEnv)) {
|
|
417
|
+
if (key.startsWith('AWS_') || key === 'CLAUDE_CODE_USE_BEDROCK') delete hubClientEnv[key];
|
|
418
|
+
}
|
|
419
|
+
if (!hubClientEnv.ANTHROPIC_API_KEY) {
|
|
420
|
+
hubClientEnv.ANTHROPIC_API_KEY = 'bedrock-via-ccxray';
|
|
421
|
+
}
|
|
422
|
+
}
|
|
408
423
|
const child = spawn('claude', claudeArgs, {
|
|
409
424
|
stdio: 'inherit',
|
|
410
|
-
env:
|
|
425
|
+
env: hubClientEnv,
|
|
411
426
|
});
|
|
412
427
|
child.on('error', (err) => {
|
|
413
428
|
if (err.code === 'ENOENT') {
|
|
@@ -435,13 +450,13 @@ async function startServer() {
|
|
|
435
450
|
if (config.IS_BEDROCK_MODE) {
|
|
436
451
|
const activationSource = config.BEDROCK_ACTIVATION_SOURCE
|
|
437
452
|
|| (process.argv.includes('--bedrock') ? '--bedrock flag' : 'unknown');
|
|
438
|
-
if (config.
|
|
453
|
+
if (config.AWS_BEARER_TOKEN_BEDROCK) {
|
|
439
454
|
_origLog(`\x1b[36mBedrock mode: ${config.BEDROCK_RESOLVED_REGION} (bearer token auth, via ${activationSource})\x1b[0m`);
|
|
440
455
|
} else {
|
|
441
456
|
const { resolveCredentials } = require('./bedrock-credentials');
|
|
442
457
|
const creds = await resolveCredentials();
|
|
443
458
|
if (!creds) {
|
|
444
|
-
console.error('\x1b[31mBedrock mode requires auth. Set
|
|
459
|
+
console.error('\x1b[31mBedrock mode requires auth. Set AWS_BEARER_TOKEN_BEDROCK, AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, or configure ~/.aws/credentials\x1b[0m');
|
|
445
460
|
process.exit(1);
|
|
446
461
|
}
|
|
447
462
|
config.BEDROCK_CREDENTIALS = creds;
|