@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 CHANGED
@@ -499,7 +499,7 @@ function canonicalPolicyJSON(value) {
499
499
  }
500
500
 
501
501
  // src/domain.ts
502
- var VERSION = "0.1.4";
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.4",
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 reader = response.body.getReader();
6111
- let ended = false, output;
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 reader = response.body.getReader(), decoder = new TextDecoder;
6477
+ const decoder = new TextDecoder;
6409
6478
  const sse = response.headers.get("content-type")?.includes("text/event-stream");
6410
- let buffer = "", ended = false, output;
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 end2 = buffer.indexOf(`
6494
+ let end = buffer.indexOf(`
6426
6495
  `);
6427
- while (end2 >= 0) {
6428
- const line = buffer.slice(0, end2).trim();
6429
- buffer = buffer.slice(end2 + 1);
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
- end2 = buffer.indexOf(`
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 end = (error) => {
6443
- if (ended)
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 = async () => {
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 reader = upstream.body.getReader();
7879
- let ended = false;
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 reader = response.body.getReader();
8593
- let ended = false;
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.4";
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.4";
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
+ };
@@ -62,7 +62,7 @@ function canonicalPolicyJSON(value) {
62
62
  }
63
63
 
64
64
  // src/domain.ts
65
- var VERSION = "0.1.4";
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.4",
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
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.0.3",
3
3
  "info": {
4
4
  "title": "Switcher API",
5
- "version": "0.1.4",
5
+ "version": "0.1.5",
6
6
  "description": "Authenticated provider/profile/catalog control plane. Launches run locally; the API never returns provider credentials."
7
7
  },
8
8
  "security": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/switcher",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "description": "Universal provider and model switcher for coding harnesses with an API, SDK and self-hosted storage",
6
6
  "license": "Apache-2.0",