omnigateway 0.1.2 → 0.1.3
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/bin/omni.js +39 -14
- package/gateway.js +77 -18
- package/package.json +1 -1
package/bin/omni.js
CHANGED
|
@@ -608,6 +608,7 @@ async function* decodeAnthropic(messages) {
|
|
|
608
608
|
let cacheWriteTokens = 0;
|
|
609
609
|
let outputTokens = 0;
|
|
610
610
|
let stopReason = "endTurn";
|
|
611
|
+
let terminal = false;
|
|
611
612
|
for await (const msg of messages) {
|
|
612
613
|
const d = json(msg.data);
|
|
613
614
|
if (d === null)
|
|
@@ -673,6 +674,7 @@ async function* decodeAnthropic(messages) {
|
|
|
673
674
|
break;
|
|
674
675
|
}
|
|
675
676
|
case "message_stop":
|
|
677
|
+
terminal = true;
|
|
676
678
|
yield {
|
|
677
679
|
type: "end",
|
|
678
680
|
stopReason,
|
|
@@ -680,6 +682,7 @@ async function* decodeAnthropic(messages) {
|
|
|
680
682
|
};
|
|
681
683
|
break;
|
|
682
684
|
case "error": {
|
|
685
|
+
terminal = true;
|
|
683
686
|
const code = ERROR_TYPE[String(d.error?.type)] ?? "UPSTREAM";
|
|
684
687
|
yield {
|
|
685
688
|
type: "error",
|
|
@@ -693,6 +696,14 @@ async function* decodeAnthropic(messages) {
|
|
|
693
696
|
break;
|
|
694
697
|
}
|
|
695
698
|
}
|
|
699
|
+
if (!terminal) {
|
|
700
|
+
yield {
|
|
701
|
+
type: "error",
|
|
702
|
+
code: "UPSTREAM",
|
|
703
|
+
message: "upstream stream ended before message_stop",
|
|
704
|
+
retryable: RETRYABLE.UPSTREAM
|
|
705
|
+
};
|
|
706
|
+
}
|
|
696
707
|
}
|
|
697
708
|
|
|
698
709
|
// packages/providers/src/anthropic/wire.ts
|
|
@@ -805,9 +816,10 @@ var anthropicAdapter = {
|
|
|
805
816
|
["anthropic-version", API_VERSION],
|
|
806
817
|
["Accept", req.request.stream ? "text/event-stream" : "application/json"]
|
|
807
818
|
];
|
|
819
|
+
const betas = new Set(req.request.betas ?? []);
|
|
808
820
|
if (oauth) {
|
|
809
821
|
protocol.push(["Authorization", `Bearer ${req.credentials.accessToken}`]);
|
|
810
|
-
|
|
822
|
+
betas.add(OAUTH_BETA);
|
|
811
823
|
} else if (req.credentials.apiKey !== null) {
|
|
812
824
|
protocol.push(["x-api-key", req.credentials.apiKey]);
|
|
813
825
|
} else {
|
|
@@ -815,6 +827,8 @@ var anthropicAdapter = {
|
|
|
815
827
|
provider: "anthropic"
|
|
816
828
|
});
|
|
817
829
|
}
|
|
830
|
+
if (betas.size > 0)
|
|
831
|
+
protocol.push(["anthropic-beta", [...betas].join(",")]);
|
|
818
832
|
const profile = PROFILES.anthropic;
|
|
819
833
|
const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
|
|
820
834
|
const bodyString = signAnthropicBody(JSON.stringify(orderFields(withSystem, BODY_ORDER.anthropic)));
|
|
@@ -1032,18 +1046,16 @@ async function* decodeChat(messages) {
|
|
|
1032
1046
|
let started = false;
|
|
1033
1047
|
let textOpen = false;
|
|
1034
1048
|
let textIndex;
|
|
1035
|
-
let
|
|
1049
|
+
let done = false;
|
|
1036
1050
|
let stopReason = "endTurn";
|
|
1037
1051
|
let usage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
|
|
1038
1052
|
const toolIndex = new Map;
|
|
1039
1053
|
let nextIndex = 0;
|
|
1040
|
-
const emitEnd = () => {
|
|
1041
|
-
ended = true;
|
|
1042
|
-
return { type: "end", stopReason, usage };
|
|
1043
|
-
};
|
|
1044
1054
|
for await (const msg of messages) {
|
|
1045
|
-
if (msg.data === "[DONE]")
|
|
1055
|
+
if (msg.data === "[DONE]") {
|
|
1056
|
+
done = true;
|
|
1046
1057
|
break;
|
|
1058
|
+
}
|
|
1047
1059
|
const d = json2(msg.data);
|
|
1048
1060
|
if (d === null)
|
|
1049
1061
|
continue;
|
|
@@ -1098,19 +1110,21 @@ async function* decodeChat(messages) {
|
|
|
1098
1110
|
}
|
|
1099
1111
|
if (typeof choice.finish_reason === "string") {
|
|
1100
1112
|
stopReason = FINISH[choice.finish_reason] ?? "endTurn";
|
|
1101
|
-
if (textOpen)
|
|
1102
|
-
yield { type: "blockEnd", index: textIndex ?? 0 };
|
|
1103
|
-
for (const index of toolIndex.values())
|
|
1104
|
-
yield { type: "blockEnd", index };
|
|
1105
|
-
yield emitEnd();
|
|
1106
1113
|
}
|
|
1107
1114
|
}
|
|
1108
|
-
if (
|
|
1115
|
+
if (done) {
|
|
1109
1116
|
if (textOpen)
|
|
1110
1117
|
yield { type: "blockEnd", index: textIndex ?? 0 };
|
|
1111
1118
|
for (const index of toolIndex.values())
|
|
1112
1119
|
yield { type: "blockEnd", index };
|
|
1113
|
-
yield
|
|
1120
|
+
yield { type: "end", stopReason, usage };
|
|
1121
|
+
} else {
|
|
1122
|
+
yield {
|
|
1123
|
+
type: "error",
|
|
1124
|
+
code: "UPSTREAM",
|
|
1125
|
+
message: "upstream stream ended before [DONE]",
|
|
1126
|
+
retryable: RETRYABLE.UPSTREAM
|
|
1127
|
+
};
|
|
1114
1128
|
}
|
|
1115
1129
|
}
|
|
1116
1130
|
|
|
@@ -1265,6 +1279,7 @@ async function* decodeResponses(messages) {
|
|
|
1265
1279
|
return assigned;
|
|
1266
1280
|
};
|
|
1267
1281
|
let sawToolCall = false;
|
|
1282
|
+
let terminal = false;
|
|
1268
1283
|
const ownsBlock = new Set;
|
|
1269
1284
|
for await (const msg of messages) {
|
|
1270
1285
|
const d = json3(msg.data);
|
|
@@ -1340,6 +1355,7 @@ async function* decodeResponses(messages) {
|
|
|
1340
1355
|
}
|
|
1341
1356
|
case "response.completed":
|
|
1342
1357
|
case "response.incomplete": {
|
|
1358
|
+
terminal = true;
|
|
1343
1359
|
const r = d.response ?? {};
|
|
1344
1360
|
const reason = r.incomplete_details?.reason;
|
|
1345
1361
|
let stopReason = sawToolCall ? "toolUse" : "endTurn";
|
|
@@ -1361,6 +1377,7 @@ async function* decodeResponses(messages) {
|
|
|
1361
1377
|
}
|
|
1362
1378
|
case "response.failed":
|
|
1363
1379
|
case "error": {
|
|
1380
|
+
terminal = true;
|
|
1364
1381
|
const err = d.response?.error ?? d.error ?? {};
|
|
1365
1382
|
const code = ERROR_CODE[String(err.code ?? err.type)] ?? "UPSTREAM";
|
|
1366
1383
|
yield {
|
|
@@ -1375,6 +1392,14 @@ async function* decodeResponses(messages) {
|
|
|
1375
1392
|
break;
|
|
1376
1393
|
}
|
|
1377
1394
|
}
|
|
1395
|
+
if (!terminal) {
|
|
1396
|
+
yield {
|
|
1397
|
+
type: "error",
|
|
1398
|
+
code: "UPSTREAM",
|
|
1399
|
+
message: "upstream stream ended before response completion",
|
|
1400
|
+
retryable: RETRYABLE.UPSTREAM
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1378
1403
|
}
|
|
1379
1404
|
|
|
1380
1405
|
// packages/providers/src/openai/wire.ts
|
package/gateway.js
CHANGED
|
@@ -5866,6 +5866,7 @@ async function* decodeAnthropic(messages) {
|
|
|
5866
5866
|
let cacheWriteTokens = 0;
|
|
5867
5867
|
let outputTokens = 0;
|
|
5868
5868
|
let stopReason = "endTurn";
|
|
5869
|
+
let terminal = false;
|
|
5869
5870
|
for await (const msg of messages) {
|
|
5870
5871
|
const d = json(msg.data);
|
|
5871
5872
|
if (d === null)
|
|
@@ -5931,6 +5932,7 @@ async function* decodeAnthropic(messages) {
|
|
|
5931
5932
|
break;
|
|
5932
5933
|
}
|
|
5933
5934
|
case "message_stop":
|
|
5935
|
+
terminal = true;
|
|
5934
5936
|
yield {
|
|
5935
5937
|
type: "end",
|
|
5936
5938
|
stopReason,
|
|
@@ -5938,6 +5940,7 @@ async function* decodeAnthropic(messages) {
|
|
|
5938
5940
|
};
|
|
5939
5941
|
break;
|
|
5940
5942
|
case "error": {
|
|
5943
|
+
terminal = true;
|
|
5941
5944
|
const code = ERROR_TYPE[String(d.error?.type)] ?? "UPSTREAM";
|
|
5942
5945
|
yield {
|
|
5943
5946
|
type: "error",
|
|
@@ -5951,6 +5954,14 @@ async function* decodeAnthropic(messages) {
|
|
|
5951
5954
|
break;
|
|
5952
5955
|
}
|
|
5953
5956
|
}
|
|
5957
|
+
if (!terminal) {
|
|
5958
|
+
yield {
|
|
5959
|
+
type: "error",
|
|
5960
|
+
code: "UPSTREAM",
|
|
5961
|
+
message: "upstream stream ended before message_stop",
|
|
5962
|
+
retryable: RETRYABLE.UPSTREAM
|
|
5963
|
+
};
|
|
5964
|
+
}
|
|
5954
5965
|
}
|
|
5955
5966
|
|
|
5956
5967
|
// packages/providers/src/anthropic/wire.ts
|
|
@@ -6063,9 +6074,10 @@ var anthropicAdapter = {
|
|
|
6063
6074
|
["anthropic-version", API_VERSION],
|
|
6064
6075
|
["Accept", req.request.stream ? "text/event-stream" : "application/json"]
|
|
6065
6076
|
];
|
|
6077
|
+
const betas = new Set(req.request.betas ?? []);
|
|
6066
6078
|
if (oauth) {
|
|
6067
6079
|
protocol.push(["Authorization", `Bearer ${req.credentials.accessToken}`]);
|
|
6068
|
-
|
|
6080
|
+
betas.add(OAUTH_BETA);
|
|
6069
6081
|
} else if (req.credentials.apiKey !== null) {
|
|
6070
6082
|
protocol.push(["x-api-key", req.credentials.apiKey]);
|
|
6071
6083
|
} else {
|
|
@@ -6073,6 +6085,8 @@ var anthropicAdapter = {
|
|
|
6073
6085
|
provider: "anthropic"
|
|
6074
6086
|
});
|
|
6075
6087
|
}
|
|
6088
|
+
if (betas.size > 0)
|
|
6089
|
+
protocol.push(["anthropic-beta", [...betas].join(",")]);
|
|
6076
6090
|
const profile = PROFILES.anthropic;
|
|
6077
6091
|
const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
|
|
6078
6092
|
const bodyString = signAnthropicBody(JSON.stringify(orderFields(withSystem, BODY_ORDER.anthropic)));
|
|
@@ -6290,18 +6304,16 @@ async function* decodeChat(messages) {
|
|
|
6290
6304
|
let started = false;
|
|
6291
6305
|
let textOpen = false;
|
|
6292
6306
|
let textIndex;
|
|
6293
|
-
let
|
|
6307
|
+
let done = false;
|
|
6294
6308
|
let stopReason = "endTurn";
|
|
6295
6309
|
let usage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
|
|
6296
6310
|
const toolIndex = new Map;
|
|
6297
6311
|
let nextIndex = 0;
|
|
6298
|
-
const emitEnd = () => {
|
|
6299
|
-
ended = true;
|
|
6300
|
-
return { type: "end", stopReason, usage };
|
|
6301
|
-
};
|
|
6302
6312
|
for await (const msg of messages) {
|
|
6303
|
-
if (msg.data === "[DONE]")
|
|
6313
|
+
if (msg.data === "[DONE]") {
|
|
6314
|
+
done = true;
|
|
6304
6315
|
break;
|
|
6316
|
+
}
|
|
6305
6317
|
const d = json2(msg.data);
|
|
6306
6318
|
if (d === null)
|
|
6307
6319
|
continue;
|
|
@@ -6356,19 +6368,21 @@ async function* decodeChat(messages) {
|
|
|
6356
6368
|
}
|
|
6357
6369
|
if (typeof choice.finish_reason === "string") {
|
|
6358
6370
|
stopReason = FINISH[choice.finish_reason] ?? "endTurn";
|
|
6359
|
-
if (textOpen)
|
|
6360
|
-
yield { type: "blockEnd", index: textIndex ?? 0 };
|
|
6361
|
-
for (const index of toolIndex.values())
|
|
6362
|
-
yield { type: "blockEnd", index };
|
|
6363
|
-
yield emitEnd();
|
|
6364
6371
|
}
|
|
6365
6372
|
}
|
|
6366
|
-
if (
|
|
6373
|
+
if (done) {
|
|
6367
6374
|
if (textOpen)
|
|
6368
6375
|
yield { type: "blockEnd", index: textIndex ?? 0 };
|
|
6369
6376
|
for (const index of toolIndex.values())
|
|
6370
6377
|
yield { type: "blockEnd", index };
|
|
6371
|
-
yield
|
|
6378
|
+
yield { type: "end", stopReason, usage };
|
|
6379
|
+
} else {
|
|
6380
|
+
yield {
|
|
6381
|
+
type: "error",
|
|
6382
|
+
code: "UPSTREAM",
|
|
6383
|
+
message: "upstream stream ended before [DONE]",
|
|
6384
|
+
retryable: RETRYABLE.UPSTREAM
|
|
6385
|
+
};
|
|
6372
6386
|
}
|
|
6373
6387
|
}
|
|
6374
6388
|
|
|
@@ -6523,6 +6537,7 @@ async function* decodeResponses(messages) {
|
|
|
6523
6537
|
return assigned;
|
|
6524
6538
|
};
|
|
6525
6539
|
let sawToolCall = false;
|
|
6540
|
+
let terminal = false;
|
|
6526
6541
|
const ownsBlock = new Set;
|
|
6527
6542
|
for await (const msg of messages) {
|
|
6528
6543
|
const d = json3(msg.data);
|
|
@@ -6598,6 +6613,7 @@ async function* decodeResponses(messages) {
|
|
|
6598
6613
|
}
|
|
6599
6614
|
case "response.completed":
|
|
6600
6615
|
case "response.incomplete": {
|
|
6616
|
+
terminal = true;
|
|
6601
6617
|
const r = d.response ?? {};
|
|
6602
6618
|
const reason = r.incomplete_details?.reason;
|
|
6603
6619
|
let stopReason = sawToolCall ? "toolUse" : "endTurn";
|
|
@@ -6619,6 +6635,7 @@ async function* decodeResponses(messages) {
|
|
|
6619
6635
|
}
|
|
6620
6636
|
case "response.failed":
|
|
6621
6637
|
case "error": {
|
|
6638
|
+
terminal = true;
|
|
6622
6639
|
const err = d.response?.error ?? d.error ?? {};
|
|
6623
6640
|
const code = ERROR_CODE[String(err.code ?? err.type)] ?? "UPSTREAM";
|
|
6624
6641
|
yield {
|
|
@@ -6633,6 +6650,14 @@ async function* decodeResponses(messages) {
|
|
|
6633
6650
|
break;
|
|
6634
6651
|
}
|
|
6635
6652
|
}
|
|
6653
|
+
if (!terminal) {
|
|
6654
|
+
yield {
|
|
6655
|
+
type: "error",
|
|
6656
|
+
code: "UPSTREAM",
|
|
6657
|
+
message: "upstream stream ended before response completion",
|
|
6658
|
+
retryable: RETRYABLE.UPSTREAM
|
|
6659
|
+
};
|
|
6660
|
+
}
|
|
6636
6661
|
}
|
|
6637
6662
|
|
|
6638
6663
|
// packages/providers/src/openai/wire.ts
|
|
@@ -38319,12 +38344,18 @@ async function dispatch(request2, deps, signal) {
|
|
|
38319
38344
|
}), dispatchSignal);
|
|
38320
38345
|
for (const d of result.degradations)
|
|
38321
38346
|
log.degradations.push(d);
|
|
38347
|
+
let terminal = false;
|
|
38348
|
+
const pending = [];
|
|
38322
38349
|
for await (const event of result.events) {
|
|
38323
38350
|
if (event.type === "blockDelta" && !committed) {
|
|
38324
38351
|
committed = true;
|
|
38325
38352
|
log.ttftMs = deps.now() - startedAt;
|
|
38353
|
+
for (const buffered of pending)
|
|
38354
|
+
yield buffered;
|
|
38355
|
+
pending.length = 0;
|
|
38326
38356
|
}
|
|
38327
38357
|
if (event.type === "end") {
|
|
38358
|
+
terminal = true;
|
|
38328
38359
|
log.inputTokens = event.usage.inputTokens;
|
|
38329
38360
|
log.outputTokens = event.usage.outputTokens;
|
|
38330
38361
|
log.cacheReadTokens = event.usage.cacheReadTokens;
|
|
@@ -38332,6 +38363,7 @@ async function dispatch(request2, deps, signal) {
|
|
|
38332
38363
|
log.costUsd = priceOf(candidate, event.usage);
|
|
38333
38364
|
}
|
|
38334
38365
|
if (event.type === "error") {
|
|
38366
|
+
terminal = true;
|
|
38335
38367
|
if (!committed && RETRYABLE[event.code]) {
|
|
38336
38368
|
throw new GatewayError(event.code, event.message);
|
|
38337
38369
|
}
|
|
@@ -38348,7 +38380,21 @@ async function dispatch(request2, deps, signal) {
|
|
|
38348
38380
|
log.durationMs = deps.now() - startedAt;
|
|
38349
38381
|
return;
|
|
38350
38382
|
}
|
|
38351
|
-
|
|
38383
|
+
if (!committed && event.type !== "end") {
|
|
38384
|
+
pending.push(event);
|
|
38385
|
+
} else {
|
|
38386
|
+
if (!committed) {
|
|
38387
|
+
for (const buffered of pending)
|
|
38388
|
+
yield buffered;
|
|
38389
|
+
pending.length = 0;
|
|
38390
|
+
}
|
|
38391
|
+
yield event;
|
|
38392
|
+
}
|
|
38393
|
+
if (event.type === "end")
|
|
38394
|
+
break;
|
|
38395
|
+
}
|
|
38396
|
+
if (!terminal) {
|
|
38397
|
+
throw new GatewayError("UPSTREAM", "upstream stream ended without a terminal event");
|
|
38352
38398
|
}
|
|
38353
38399
|
await persistHealth(recordSuccess(healthFor(candidate), {
|
|
38354
38400
|
settings: snapshot.settings,
|
|
@@ -38602,6 +38648,7 @@ var chunk = (id, created, model, choice, usage) => ({
|
|
|
38602
38648
|
async function* openaiStream(events, requestId, created) {
|
|
38603
38649
|
let model = "";
|
|
38604
38650
|
let roleSent = false;
|
|
38651
|
+
let completed = false;
|
|
38605
38652
|
const toolIndex = new Map;
|
|
38606
38653
|
const emit = (data) => ({ event: "message", data: JSON.stringify(data) });
|
|
38607
38654
|
for await (const event of events) {
|
|
@@ -38663,6 +38710,7 @@ async function* openaiStream(events, requestId, created) {
|
|
|
38663
38710
|
case "blockEnd":
|
|
38664
38711
|
break;
|
|
38665
38712
|
case "end":
|
|
38713
|
+
completed = true;
|
|
38666
38714
|
yield emit(chunk(requestId, created, model, { index: 0, delta: {}, finish_reason: FINISH2[event.stopReason] }, {
|
|
38667
38715
|
prompt_tokens: event.usage.inputTokens,
|
|
38668
38716
|
completion_tokens: event.usage.outputTokens,
|
|
@@ -38676,7 +38724,8 @@ async function* openaiStream(events, requestId, created) {
|
|
|
38676
38724
|
}
|
|
38677
38725
|
}
|
|
38678
38726
|
}
|
|
38679
|
-
|
|
38727
|
+
if (completed)
|
|
38728
|
+
yield { event: "message", data: "[DONE]" };
|
|
38680
38729
|
}
|
|
38681
38730
|
function openaiResponse(collected, requestId, created) {
|
|
38682
38731
|
const text = collected.content.flatMap((b) => b.type === "text" ? [b.text] : []).join("");
|
|
@@ -38855,7 +38904,14 @@ function toIrReasoning(thinking, effort) {
|
|
|
38855
38904
|
return { mode: "off" };
|
|
38856
38905
|
}
|
|
38857
38906
|
}
|
|
38858
|
-
function
|
|
38907
|
+
function readBetas(headers) {
|
|
38908
|
+
const raw = headers?.get("anthropic-beta");
|
|
38909
|
+
if (raw === undefined || raw === null)
|
|
38910
|
+
return [];
|
|
38911
|
+
const names = raw.split(",").map((name) => name.trim()).filter((name) => name.length > 0);
|
|
38912
|
+
return [...new Set(names)];
|
|
38913
|
+
}
|
|
38914
|
+
function parseAnthropicRequest(body2, headers) {
|
|
38859
38915
|
if (typeof body2 !== "object" || body2 === null) {
|
|
38860
38916
|
throw new GatewayError("BAD_REQUEST", "request body must be a JSON object");
|
|
38861
38917
|
}
|
|
@@ -38896,6 +38952,9 @@ function parseAnthropicRequest(body2) {
|
|
|
38896
38952
|
const extras = extraFields(body2, KNOWN);
|
|
38897
38953
|
if (extras !== undefined)
|
|
38898
38954
|
request2.vendor = { anthropic: extras };
|
|
38955
|
+
const betas = readBetas(headers);
|
|
38956
|
+
if (betas.length > 0)
|
|
38957
|
+
request2.betas = betas;
|
|
38899
38958
|
return validateRequest(request2);
|
|
38900
38959
|
}
|
|
38901
38960
|
|
|
@@ -39129,7 +39188,7 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
39129
39188
|
keyId = key.id;
|
|
39130
39189
|
rateLimiter.consume(key.id, key.rateLimitPerMin);
|
|
39131
39190
|
const body2 = await request2.json();
|
|
39132
|
-
const chatRequest = surface === "anthropic" ? parseAnthropicRequest(body2) : parseOpenAIRequest(body2);
|
|
39191
|
+
const chatRequest = surface === "anthropic" ? parseAnthropicRequest(body2, request2.headers) : parseOpenAIRequest(body2);
|
|
39133
39192
|
if (key.modelAllowlist !== null && !key.modelAllowlist.includes(chatRequest.model)) {
|
|
39134
39193
|
throw new GatewayError("AUTH", `model "${chatRequest.model}" is not allowed for this API key`);
|
|
39135
39194
|
}
|
package/package.json
CHANGED