@hasna/switcher 0.1.4 → 0.1.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/dist/cli/index.js +134 -193
- package/dist/domain.d.ts +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/provider-stream.d.ts +17 -0
- package/dist/serve/index.js +2 -2
- package/openapi.json +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -499,7 +499,7 @@ function canonicalPolicyJSON(value) {
|
|
|
499
499
|
}
|
|
500
500
|
|
|
501
501
|
// src/domain.ts
|
|
502
|
-
var VERSION = "0.1.
|
|
502
|
+
var VERSION = "0.1.5";
|
|
503
503
|
var harnessSchema = z3.enum(["claude", "codex", "grok", "opencode", "opencode2", "pi", "omp", "dsh", "cline", "hermes", "prime-agent", "gemini", "aider", "kilo"]);
|
|
504
504
|
var protocolSchema = z3.enum(["anthropic-messages", "openai-responses", "openai-chat", "gemini-generate-content"]);
|
|
505
505
|
var idSchema = z3.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/);
|
|
@@ -1358,6 +1358,122 @@ function clientFromEnv(env = process.env, options = {}) {
|
|
|
1358
1358
|
return SwitcherClient.fromEnvironment(env, options);
|
|
1359
1359
|
}
|
|
1360
1360
|
|
|
1361
|
+
// src/provider-stream.ts
|
|
1362
|
+
function terminalEventObserver(protocol, contentType) {
|
|
1363
|
+
const enabled = contentType?.split(";", 1)[0].trim().toLowerCase() === "text/event-stream";
|
|
1364
|
+
const decoder = new TextDecoder;
|
|
1365
|
+
let line = "", data = [], size = 0, overflow = false, afterCR = false, complete = false;
|
|
1366
|
+
const dispatchLine = () => {
|
|
1367
|
+
if (!line) {
|
|
1368
|
+
if (!overflow && data.length) {
|
|
1369
|
+
const payload = data.join(`
|
|
1370
|
+
`);
|
|
1371
|
+
if (protocol === "openai-chat")
|
|
1372
|
+
complete ||= payload === "[DONE]";
|
|
1373
|
+
else
|
|
1374
|
+
try {
|
|
1375
|
+
const value = JSON.parse(payload);
|
|
1376
|
+
complete ||= protocol === "anthropic-messages" ? value?.type === "message_stop" : protocol === "openai-responses" && ["response.completed", "response.failed", "response.incomplete"].includes(value?.type);
|
|
1377
|
+
} catch {}
|
|
1378
|
+
}
|
|
1379
|
+
data = [];
|
|
1380
|
+
size = 0;
|
|
1381
|
+
overflow = false;
|
|
1382
|
+
} else if (!overflow && line.startsWith("data:"))
|
|
1383
|
+
data.push(line.slice(5).replace(/^ /, ""));
|
|
1384
|
+
line = "";
|
|
1385
|
+
};
|
|
1386
|
+
return (chunk) => {
|
|
1387
|
+
if (!enabled || protocol === "gemini-generate-content")
|
|
1388
|
+
return false;
|
|
1389
|
+
for (const char of decoder.decode(chunk, { stream: true })) {
|
|
1390
|
+
if (afterCR && char === `
|
|
1391
|
+
`) {
|
|
1392
|
+
afterCR = false;
|
|
1393
|
+
continue;
|
|
1394
|
+
}
|
|
1395
|
+
afterCR = char === "\r";
|
|
1396
|
+
if (char === "\r" || char === `
|
|
1397
|
+
`)
|
|
1398
|
+
dispatchLine();
|
|
1399
|
+
else if (++size <= 131072)
|
|
1400
|
+
line += char;
|
|
1401
|
+
else {
|
|
1402
|
+
overflow = true;
|
|
1403
|
+
line = "oversized";
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
return complete;
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
function proxyProviderStream(input) {
|
|
1410
|
+
const reader = input.response.body.getReader();
|
|
1411
|
+
const terminal = terminalEventObserver(input.protocol, input.response.headers.get("content-type"));
|
|
1412
|
+
let ended = false, output;
|
|
1413
|
+
const end = (error) => {
|
|
1414
|
+
if (ended)
|
|
1415
|
+
return;
|
|
1416
|
+
ended = true;
|
|
1417
|
+
try {
|
|
1418
|
+
if (error)
|
|
1419
|
+
output.error(error);
|
|
1420
|
+
else
|
|
1421
|
+
output.close();
|
|
1422
|
+
} catch {}
|
|
1423
|
+
input.release();
|
|
1424
|
+
};
|
|
1425
|
+
const cancelReader = async () => {
|
|
1426
|
+
input.abort.abort();
|
|
1427
|
+
try {
|
|
1428
|
+
await reader.cancel();
|
|
1429
|
+
} catch {}
|
|
1430
|
+
};
|
|
1431
|
+
const stream = new ReadableStream({
|
|
1432
|
+
start(controller) {
|
|
1433
|
+
output = controller;
|
|
1434
|
+
},
|
|
1435
|
+
async pull(controller) {
|
|
1436
|
+
try {
|
|
1437
|
+
const chunk = await reader.read();
|
|
1438
|
+
if (ended)
|
|
1439
|
+
return;
|
|
1440
|
+
if (chunk.done) {
|
|
1441
|
+
input.inspect?.(new Uint8Array, true);
|
|
1442
|
+
end();
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
input.inspect?.(chunk.value);
|
|
1446
|
+
const complete = terminal(chunk.value);
|
|
1447
|
+
controller.enqueue(chunk.value);
|
|
1448
|
+
if (complete) {
|
|
1449
|
+
end();
|
|
1450
|
+
await cancelReader();
|
|
1451
|
+
}
|
|
1452
|
+
} catch {
|
|
1453
|
+
if (ended)
|
|
1454
|
+
return;
|
|
1455
|
+
if (input.requestSignal.aborted || input.abort.signal.aborted || input.closing())
|
|
1456
|
+
end();
|
|
1457
|
+
else {
|
|
1458
|
+
input.interrupted?.();
|
|
1459
|
+
end(new Error("Provider stream ended unexpectedly"));
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
},
|
|
1463
|
+
async cancel() {
|
|
1464
|
+
if (!ended) {
|
|
1465
|
+
ended = true;
|
|
1466
|
+
input.release();
|
|
1467
|
+
}
|
|
1468
|
+
await cancelReader();
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
return { stream, cancel: async () => {
|
|
1472
|
+
end();
|
|
1473
|
+
await cancelReader();
|
|
1474
|
+
} };
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1361
1477
|
// src/opencode-model-policy.ts
|
|
1362
1478
|
var routeForAgent = (agent) => {
|
|
1363
1479
|
const name = agent.name.toLowerCase();
|
|
@@ -2024,7 +2140,7 @@ var openapi_default = {
|
|
|
2024
2140
|
openapi: "3.0.3",
|
|
2025
2141
|
info: {
|
|
2026
2142
|
title: "Switcher API",
|
|
2027
|
-
version: "0.1.
|
|
2143
|
+
version: "0.1.5",
|
|
2028
2144
|
description: "Authenticated provider/profile/catalog control plane. Launches run locally; the API never returns provider credentials."
|
|
2029
2145
|
},
|
|
2030
2146
|
security: [
|
|
@@ -6107,55 +6223,8 @@ function geminiBridge(input) {
|
|
|
6107
6223
|
release();
|
|
6108
6224
|
return new Response(null, { status: response.status });
|
|
6109
6225
|
}
|
|
6110
|
-
const
|
|
6111
|
-
|
|
6112
|
-
const end = (error) => {
|
|
6113
|
-
if (ended)
|
|
6114
|
-
return;
|
|
6115
|
-
ended = true;
|
|
6116
|
-
try {
|
|
6117
|
-
if (error && !closing)
|
|
6118
|
-
output.error(error);
|
|
6119
|
-
else
|
|
6120
|
-
output.close();
|
|
6121
|
-
} catch {}
|
|
6122
|
-
release();
|
|
6123
|
-
};
|
|
6124
|
-
const stream = new ReadableStream({
|
|
6125
|
-
start(controller) {
|
|
6126
|
-
output = controller;
|
|
6127
|
-
},
|
|
6128
|
-
async pull(controller) {
|
|
6129
|
-
try {
|
|
6130
|
-
const chunk = await reader.read();
|
|
6131
|
-
if (ended)
|
|
6132
|
-
return;
|
|
6133
|
-
if (chunk.done)
|
|
6134
|
-
end();
|
|
6135
|
-
else
|
|
6136
|
-
controller.enqueue(chunk.value);
|
|
6137
|
-
} catch {
|
|
6138
|
-
end(new Error("Provider stream ended unexpectedly"));
|
|
6139
|
-
}
|
|
6140
|
-
},
|
|
6141
|
-
async cancel() {
|
|
6142
|
-
ended = true;
|
|
6143
|
-
record2.abort.abort();
|
|
6144
|
-
try {
|
|
6145
|
-
await reader.cancel();
|
|
6146
|
-
} finally {
|
|
6147
|
-
release();
|
|
6148
|
-
}
|
|
6149
|
-
}
|
|
6150
|
-
});
|
|
6151
|
-
record2.cancel = async () => {
|
|
6152
|
-
record2.abort.abort();
|
|
6153
|
-
try {
|
|
6154
|
-
await reader.cancel();
|
|
6155
|
-
} catch {} finally {
|
|
6156
|
-
end();
|
|
6157
|
-
}
|
|
6158
|
-
};
|
|
6226
|
+
const { stream, cancel } = proxyProviderStream({ response, protocol: input.protocol, requestSignal: request.signal, abort: record2.abort, closing: () => closing, release });
|
|
6227
|
+
record2.cancel = cancel;
|
|
6159
6228
|
return new Response(stream, { status: response.status, headers: { "content-type": response.headers.get("content-type") ?? "application/json", "cache-control": "no-store" } });
|
|
6160
6229
|
} catch {
|
|
6161
6230
|
release();
|
|
@@ -6405,9 +6474,9 @@ function createInferenceGateway(input) {
|
|
|
6405
6474
|
release();
|
|
6406
6475
|
return new Response(null, { status: response.status });
|
|
6407
6476
|
}
|
|
6408
|
-
const
|
|
6477
|
+
const decoder = new TextDecoder;
|
|
6409
6478
|
const sse = response.headers.get("content-type")?.includes("text/event-stream");
|
|
6410
|
-
let buffer = ""
|
|
6479
|
+
let buffer = "";
|
|
6411
6480
|
const observe = (text) => {
|
|
6412
6481
|
try {
|
|
6413
6482
|
const value = JSON.parse(text);
|
|
@@ -6422,14 +6491,14 @@ function createInferenceGateway(input) {
|
|
|
6422
6491
|
const inspect = (chunk, done = false) => {
|
|
6423
6492
|
buffer += decoder.decode(chunk, { stream: !done });
|
|
6424
6493
|
if (sse) {
|
|
6425
|
-
let
|
|
6494
|
+
let end = buffer.indexOf(`
|
|
6426
6495
|
`);
|
|
6427
|
-
while (
|
|
6428
|
-
const line = buffer.slice(0,
|
|
6429
|
-
buffer = buffer.slice(
|
|
6496
|
+
while (end >= 0) {
|
|
6497
|
+
const line = buffer.slice(0, end).trim();
|
|
6498
|
+
buffer = buffer.slice(end + 1);
|
|
6430
6499
|
if (line.startsWith("data:"))
|
|
6431
6500
|
observe(line.slice(5).trim());
|
|
6432
|
-
|
|
6501
|
+
end = buffer.indexOf(`
|
|
6433
6502
|
`);
|
|
6434
6503
|
}
|
|
6435
6504
|
if (buffer.length > 131072)
|
|
@@ -6439,53 +6508,10 @@ function createInferenceGateway(input) {
|
|
|
6439
6508
|
if (done && buffer)
|
|
6440
6509
|
observe(buffer);
|
|
6441
6510
|
};
|
|
6442
|
-
const
|
|
6443
|
-
|
|
6444
|
-
return;
|
|
6445
|
-
ended = true;
|
|
6446
|
-
try {
|
|
6447
|
-
if (error && !closing)
|
|
6448
|
-
output.error(error);
|
|
6449
|
-
else
|
|
6450
|
-
output.close();
|
|
6451
|
-
} catch {}
|
|
6452
|
-
release();
|
|
6453
|
-
};
|
|
6454
|
-
const stream = new ReadableStream({ start(controller) {
|
|
6455
|
-
output = controller;
|
|
6456
|
-
}, async pull(controller) {
|
|
6457
|
-
try {
|
|
6458
|
-
const chunk = await reader.read();
|
|
6459
|
-
if (ended)
|
|
6460
|
-
return;
|
|
6461
|
-
if (chunk.done) {
|
|
6462
|
-
inspect(new Uint8Array, true);
|
|
6463
|
-
end();
|
|
6464
|
-
} else {
|
|
6465
|
-
inspect(chunk.value);
|
|
6466
|
-
controller.enqueue(chunk.value);
|
|
6467
|
-
}
|
|
6468
|
-
} catch {
|
|
6469
|
-
current.reason = "stream_interrupted";
|
|
6470
|
-
end(new Error("Provider stream ended unexpectedly"));
|
|
6471
|
-
}
|
|
6472
|
-
}, async cancel() {
|
|
6473
|
-
ended = true;
|
|
6474
|
-
abort.abort();
|
|
6475
|
-
try {
|
|
6476
|
-
await reader.cancel();
|
|
6477
|
-
} finally {
|
|
6478
|
-
release();
|
|
6479
|
-
}
|
|
6511
|
+
const { stream, cancel } = proxyProviderStream({ response, protocol: input.protocol, requestSignal: request.signal, abort, closing: () => closing, release, inspect, interrupted: () => {
|
|
6512
|
+
current.reason = "stream_interrupted";
|
|
6480
6513
|
} });
|
|
6481
|
-
record2.cancel =
|
|
6482
|
-
abort.abort();
|
|
6483
|
-
try {
|
|
6484
|
-
await reader.cancel();
|
|
6485
|
-
} catch {} finally {
|
|
6486
|
-
end();
|
|
6487
|
-
}
|
|
6488
|
-
};
|
|
6514
|
+
record2.cancel = cancel;
|
|
6489
6515
|
return new Response(stream, { status: response.status, headers: { "content-type": response.headers.get("content-type") ?? "application/json", "cache-control": "no-store" } });
|
|
6490
6516
|
} catch (error) {
|
|
6491
6517
|
if (error instanceof Fault)
|
|
@@ -7875,45 +7901,8 @@ function createHermesBridge(input) {
|
|
|
7875
7901
|
release();
|
|
7876
7902
|
return new Response(null, { status: upstream.status });
|
|
7877
7903
|
}
|
|
7878
|
-
const
|
|
7879
|
-
|
|
7880
|
-
const end = () => {
|
|
7881
|
-
if (ended)
|
|
7882
|
-
return;
|
|
7883
|
-
ended = true;
|
|
7884
|
-
release();
|
|
7885
|
-
};
|
|
7886
|
-
const stream = new ReadableStream({
|
|
7887
|
-
async pull(controller) {
|
|
7888
|
-
try {
|
|
7889
|
-
const chunk = await reader.read();
|
|
7890
|
-
if (chunk.done) {
|
|
7891
|
-
end();
|
|
7892
|
-
controller.close();
|
|
7893
|
-
} else
|
|
7894
|
-
controller.enqueue(chunk.value);
|
|
7895
|
-
} catch {
|
|
7896
|
-
end();
|
|
7897
|
-
controller.error(new Error("Provider stream ended unexpectedly"));
|
|
7898
|
-
}
|
|
7899
|
-
},
|
|
7900
|
-
async cancel() {
|
|
7901
|
-
ended = true;
|
|
7902
|
-
abortController.abort();
|
|
7903
|
-
try {
|
|
7904
|
-
await reader.cancel();
|
|
7905
|
-
} finally {
|
|
7906
|
-
release();
|
|
7907
|
-
}
|
|
7908
|
-
}
|
|
7909
|
-
});
|
|
7910
|
-
record3.cancel = async () => {
|
|
7911
|
-
abortController.abort();
|
|
7912
|
-
try {
|
|
7913
|
-
await reader.cancel();
|
|
7914
|
-
} catch {}
|
|
7915
|
-
end();
|
|
7916
|
-
};
|
|
7904
|
+
const { stream, cancel } = proxyProviderStream({ response: upstream, protocol: input.protocol, requestSignal: request.signal, abort: abortController, closing: () => closing, release });
|
|
7905
|
+
record3.cancel = cancel;
|
|
7917
7906
|
return new Response(stream, {
|
|
7918
7907
|
status: upstream.status,
|
|
7919
7908
|
headers: {
|
|
@@ -8589,56 +8578,8 @@ function grokBridge(input) {
|
|
|
8589
8578
|
release();
|
|
8590
8579
|
return new Response(null, { status: response.status });
|
|
8591
8580
|
}
|
|
8592
|
-
const
|
|
8593
|
-
|
|
8594
|
-
let output;
|
|
8595
|
-
const end = (error) => {
|
|
8596
|
-
if (ended)
|
|
8597
|
-
return;
|
|
8598
|
-
ended = true;
|
|
8599
|
-
try {
|
|
8600
|
-
if (error && !closing)
|
|
8601
|
-
output.error(error);
|
|
8602
|
-
else
|
|
8603
|
-
output.close();
|
|
8604
|
-
} catch {}
|
|
8605
|
-
release();
|
|
8606
|
-
};
|
|
8607
|
-
const stream = new ReadableStream({
|
|
8608
|
-
start(controller) {
|
|
8609
|
-
output = controller;
|
|
8610
|
-
},
|
|
8611
|
-
async pull(controller) {
|
|
8612
|
-
try {
|
|
8613
|
-
const chunk = await reader.read();
|
|
8614
|
-
if (ended)
|
|
8615
|
-
return;
|
|
8616
|
-
if (chunk.done)
|
|
8617
|
-
end();
|
|
8618
|
-
else
|
|
8619
|
-
controller.enqueue(chunk.value);
|
|
8620
|
-
} catch {
|
|
8621
|
-
end(new Error("Provider stream ended unexpectedly"));
|
|
8622
|
-
}
|
|
8623
|
-
},
|
|
8624
|
-
async cancel() {
|
|
8625
|
-
ended = true;
|
|
8626
|
-
record3.abort.abort();
|
|
8627
|
-
try {
|
|
8628
|
-
await reader.cancel();
|
|
8629
|
-
} finally {
|
|
8630
|
-
release();
|
|
8631
|
-
}
|
|
8632
|
-
}
|
|
8633
|
-
});
|
|
8634
|
-
record3.cancel = async () => {
|
|
8635
|
-
record3.abort.abort();
|
|
8636
|
-
try {
|
|
8637
|
-
await reader.cancel();
|
|
8638
|
-
} catch {} finally {
|
|
8639
|
-
end();
|
|
8640
|
-
}
|
|
8641
|
-
};
|
|
8581
|
+
const { stream, cancel } = proxyProviderStream({ response, protocol: input.protocol, requestSignal: request.signal, abort: record3.abort, closing: () => closing, release });
|
|
8582
|
+
record3.cancel = cancel;
|
|
8642
8583
|
return new Response(stream, { status: response.status, headers: { "content-type": response.headers.get("content-type") ?? "application/json", "cache-control": "no-store" } });
|
|
8643
8584
|
} catch {
|
|
8644
8585
|
release();
|
package/dist/domain.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type RoutingEvent } from "./model-policy-schema";
|
|
|
3
3
|
export { modelPolicySchema, routingEventSchema, routingEventsSchema } from "./model-policy-schema";
|
|
4
4
|
export type { ModelPolicy, RoutingEvent } from "./model-policy-schema";
|
|
5
5
|
export type { AuthStyle } from "./auth";
|
|
6
|
-
export declare const VERSION = "0.1.
|
|
6
|
+
export declare const VERSION = "0.1.5";
|
|
7
7
|
export declare const harnessSchema: z.ZodEnum<["claude", "codex", "grok", "opencode", "opencode2", "pi", "omp", "dsh", "cline", "hermes", "prime-agent", "gemini", "aider", "kilo"]>;
|
|
8
8
|
export declare const protocolSchema: z.ZodEnum<["anthropic-messages", "openai-responses", "openai-chat", "gemini-generate-content"]>;
|
|
9
9
|
export declare const idSchema: z.ZodString;
|
package/dist/mcp/index.js
CHANGED
|
@@ -55,7 +55,7 @@ var routingEventSchema = z.object({
|
|
|
55
55
|
var routingEventsSchema = z.array(routingEventSchema).max(1000);
|
|
56
56
|
|
|
57
57
|
// src/domain.ts
|
|
58
|
-
var VERSION = "0.1.
|
|
58
|
+
var VERSION = "0.1.5";
|
|
59
59
|
var harnessSchema = z2.enum(["claude", "codex", "grok", "opencode", "opencode2", "pi", "omp", "dsh", "cline", "hermes", "prime-agent", "gemini", "aider", "kilo"]);
|
|
60
60
|
var protocolSchema = z2.enum(["anthropic-messages", "openai-responses", "openai-chat", "gemini-generate-content"]);
|
|
61
61
|
var idSchema = z2.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HarnessLaunchInput } from "./harness-types";
|
|
2
|
+
/** Recognize whole SSE terminal events, never marker text inside a delta. */
|
|
3
|
+
export declare function terminalEventObserver(protocol: HarnessLaunchInput["protocol"], contentType: string | null): (chunk: Uint8Array) => boolean;
|
|
4
|
+
/** One lifecycle for all bridges: terminal SSE, caller cancellation, and real failure. */
|
|
5
|
+
export declare function proxyProviderStream(input: {
|
|
6
|
+
response: Response;
|
|
7
|
+
protocol: HarnessLaunchInput["protocol"];
|
|
8
|
+
requestSignal: AbortSignal;
|
|
9
|
+
abort: AbortController;
|
|
10
|
+
closing: () => boolean;
|
|
11
|
+
release: () => void;
|
|
12
|
+
inspect?: (chunk: Uint8Array, done?: boolean) => void;
|
|
13
|
+
interrupted?: () => void;
|
|
14
|
+
}): {
|
|
15
|
+
stream: ReadableStream<Uint8Array>;
|
|
16
|
+
cancel: () => Promise<void>;
|
|
17
|
+
};
|
package/dist/serve/index.js
CHANGED
|
@@ -62,7 +62,7 @@ function canonicalPolicyJSON(value) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// src/domain.ts
|
|
65
|
-
var VERSION = "0.1.
|
|
65
|
+
var VERSION = "0.1.5";
|
|
66
66
|
var harnessSchema = z2.enum(["claude", "codex", "grok", "opencode", "opencode2", "pi", "omp", "dsh", "cline", "hermes", "prime-agent", "gemini", "aider", "kilo"]);
|
|
67
67
|
var protocolSchema = z2.enum(["anthropic-messages", "openai-responses", "openai-chat", "gemini-generate-content"]);
|
|
68
68
|
var idSchema = z2.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/);
|
|
@@ -808,7 +808,7 @@ var openapi_default = {
|
|
|
808
808
|
openapi: "3.0.3",
|
|
809
809
|
info: {
|
|
810
810
|
title: "Switcher API",
|
|
811
|
-
version: "0.1.
|
|
811
|
+
version: "0.1.5",
|
|
812
812
|
description: "Authenticated provider/profile/catalog control plane. Launches run locally; the API never returns provider credentials."
|
|
813
813
|
},
|
|
814
814
|
security: [
|
package/openapi.json
CHANGED
package/package.json
CHANGED