copillm 0.1.4 → 0.2.0
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 +1 -1
- package/dist/agentconfig/render.js +169 -25
- package/dist/cli/configCommands.js +15 -2
- package/dist/cli/launchAgent.js +8 -0
- package/dist/cli/processSafetyNet.js +52 -0
- package/dist/cli/resolveAgent.js +4 -2
- package/dist/cli.js +139 -18
- package/dist/config/home.js +3 -0
- package/dist/config/logging.js +27 -5
- package/dist/models/anthropicDefaults.js +1 -0
- package/dist/server/proxy.js +292 -26
- package/dist/server/requestLifecycle.js +115 -0
- package/dist/translation/streamingOpenAIToAnthropic.js +77 -6
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { isBenignSocketError } from "../server/requestLifecycle.js";
|
|
2
3
|
const PING_INTERVAL_MS = 1000;
|
|
3
4
|
export async function translateOpenAIStreamToAnthropic(options) {
|
|
4
5
|
const { upstream, downstream } = options;
|
|
@@ -16,11 +17,37 @@ export async function translateOpenAIStreamToAnthropic(options) {
|
|
|
16
17
|
let nextAnthropicIndex = 0;
|
|
17
18
|
let streamErrored = false;
|
|
18
19
|
let pingTimer = null;
|
|
20
|
+
let downstreamGone = false;
|
|
21
|
+
function isDownstreamAlive() {
|
|
22
|
+
if (downstreamGone)
|
|
23
|
+
return false;
|
|
24
|
+
return downstream.writable && !downstream.writableEnded && !downstream.destroyed;
|
|
25
|
+
}
|
|
26
|
+
function markDownstreamGone() {
|
|
27
|
+
if (downstreamGone)
|
|
28
|
+
return;
|
|
29
|
+
downstreamGone = true;
|
|
30
|
+
stopPings();
|
|
31
|
+
// Best-effort: also stop reading upstream so we don't pull a megabyte
|
|
32
|
+
// of SSE we'll never deliver.
|
|
33
|
+
try {
|
|
34
|
+
upstream.destroy();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// ignore
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
downstream.on("close", markDownstreamGone);
|
|
41
|
+
downstream.on("error", markDownstreamGone);
|
|
19
42
|
function startPings() {
|
|
20
43
|
if (pingTimer !== null) {
|
|
21
44
|
return;
|
|
22
45
|
}
|
|
23
46
|
pingTimer = setInterval(() => {
|
|
47
|
+
if (!isDownstreamAlive()) {
|
|
48
|
+
stopPings();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
24
51
|
writeEvent("ping", { type: "ping" });
|
|
25
52
|
}, PING_INTERVAL_MS);
|
|
26
53
|
if (typeof pingTimer.unref === "function") {
|
|
@@ -34,7 +61,20 @@ export async function translateOpenAIStreamToAnthropic(options) {
|
|
|
34
61
|
}
|
|
35
62
|
}
|
|
36
63
|
function writeEvent(eventName, data) {
|
|
37
|
-
|
|
64
|
+
if (!isDownstreamAlive()) {
|
|
65
|
+
markDownstreamGone();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
downstream.write(`event: ${eventName}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (isBenignSocketError(error)) {
|
|
73
|
+
markDownstreamGone();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
38
78
|
}
|
|
39
79
|
function emitMessageStart() {
|
|
40
80
|
if (messageStarted) {
|
|
@@ -198,6 +238,10 @@ export async function translateOpenAIStreamToAnthropic(options) {
|
|
|
198
238
|
startPings();
|
|
199
239
|
try {
|
|
200
240
|
for await (const chunk of upstream) {
|
|
241
|
+
if (!isDownstreamAlive()) {
|
|
242
|
+
markDownstreamGone();
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
201
245
|
const text = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
202
246
|
buffer += text;
|
|
203
247
|
let newlineIndex;
|
|
@@ -213,8 +257,15 @@ export async function translateOpenAIStreamToAnthropic(options) {
|
|
|
213
257
|
}
|
|
214
258
|
}
|
|
215
259
|
catch (error) {
|
|
216
|
-
streamErrored = true;
|
|
217
260
|
stopPings();
|
|
261
|
+
if (!isDownstreamAlive() || isBenignSocketError(error)) {
|
|
262
|
+
// Either we destroyed the upstream because downstream went away, or
|
|
263
|
+
// the upstream rejected with a benign socket error. Either way, no
|
|
264
|
+
// recovery write is possible — just stop.
|
|
265
|
+
markDownstreamGone();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
streamErrored = true;
|
|
218
269
|
emitMessageStart();
|
|
219
270
|
closeAllBlocks();
|
|
220
271
|
writeEvent("message_delta", {
|
|
@@ -227,13 +278,16 @@ export async function translateOpenAIStreamToAnthropic(options) {
|
|
|
227
278
|
error: { type: "api_error", message: error instanceof Error ? error.message : "upstream stream error" }
|
|
228
279
|
});
|
|
229
280
|
writeEvent("message_stop", { type: "message_stop" });
|
|
230
|
-
|
|
281
|
+
safeEndDownstream();
|
|
231
282
|
return;
|
|
232
283
|
}
|
|
233
284
|
if (streamErrored) {
|
|
234
285
|
return;
|
|
235
286
|
}
|
|
236
287
|
stopPings();
|
|
288
|
+
if (!isDownstreamAlive()) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
237
291
|
emitMessageStart();
|
|
238
292
|
closeAllBlocks();
|
|
239
293
|
writeEvent("message_delta", {
|
|
@@ -242,7 +296,18 @@ export async function translateOpenAIStreamToAnthropic(options) {
|
|
|
242
296
|
usage: { input_tokens: inputTokens, output_tokens: outputTokens, cache_read_input_tokens: cacheReadTokens }
|
|
243
297
|
});
|
|
244
298
|
writeEvent("message_stop", { type: "message_stop" });
|
|
245
|
-
|
|
299
|
+
safeEndDownstream();
|
|
300
|
+
function safeEndDownstream() {
|
|
301
|
+
if (downstream.writableEnded || downstream.destroyed)
|
|
302
|
+
return;
|
|
303
|
+
try {
|
|
304
|
+
downstream.end();
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
if (!isBenignSocketError(error))
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
246
311
|
}
|
|
247
312
|
/**
|
|
248
313
|
* Write the Anthropic `message_start` event (and an initial ping) to the
|
|
@@ -269,8 +334,14 @@ export function writeAnthropicPrelude(downstream, model) {
|
|
|
269
334
|
usage: { input_tokens: 0, cache_read_input_tokens: 0, output_tokens: 0 }
|
|
270
335
|
}
|
|
271
336
|
};
|
|
272
|
-
|
|
273
|
-
|
|
337
|
+
try {
|
|
338
|
+
downstream.write(`event: message_start\ndata: ${JSON.stringify(messageStart)}\n\n`);
|
|
339
|
+
downstream.write(`event: ping\ndata: ${JSON.stringify({ type: "ping" })}\n\n`);
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
if (!isBenignSocketError(error))
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
274
345
|
return { messageId };
|
|
275
346
|
}
|
|
276
347
|
function mapFinishReason(reason) {
|