llm-relay 0.72.1 → 0.72.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/dist/accounting-store-schema.js +11 -14
- package/dist/accounting-store-schema.js.map +1 -1
- package/dist/accounting-store.js +0 -2
- package/dist/accounting-store.js.map +1 -1
- package/dist/backend/envelope-validator.d.ts +22 -0
- package/dist/backend/envelope-validator.js +70 -0
- package/dist/backend/envelope-validator.js.map +1 -0
- package/dist/backend/health-prober.d.ts +59 -0
- package/dist/backend/health-prober.js +145 -0
- package/dist/backend/health-prober.js.map +1 -0
- package/dist/backend.js +2 -210
- package/dist/backend.js.map +1 -1
- package/dist/candidate-runner.d.ts +32 -0
- package/dist/candidate-runner.js +24 -14
- package/dist/candidate-runner.js.map +1 -1
- package/dist/cli.d.ts +0 -14
- package/dist/cli.js +11 -14
- package/dist/cli.js.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/context-limits.js +22 -33
- package/dist/context-limits.js.map +1 -1
- package/dist/delegate-gate/cast-necessity.js +12 -19
- package/dist/delegate-gate/cast-necessity.js.map +1 -1
- package/dist/delegate-gate/file-driver.d.ts +50 -0
- package/dist/delegate-gate/file-driver.js +23 -0
- package/dist/delegate-gate/file-driver.js.map +1 -0
- package/dist/delegate-gate/shared-state.js +9 -15
- package/dist/delegate-gate/shared-state.js.map +1 -1
- package/dist/delegate-gate/test-assertions.js +4 -6
- package/dist/delegate-gate/test-assertions.js.map +1 -1
- package/dist/keystore.js +10 -16
- package/dist/keystore.js.map +1 -1
- package/dist/sse-frames.d.ts +43 -0
- package/dist/sse-frames.js +39 -0
- package/dist/sse-frames.js.map +1 -1
- package/dist/think-tags.js +14 -45
- package/dist/think-tags.js.map +1 -1
- package/dist/tool-dialects.js +3 -4
- package/dist/tool-dialects.js.map +1 -1
- package/dist/tool-use-ids.js +32 -62
- package/dist/tool-use-ids.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is a provider's response envelope structurally usable by the response mappers?
|
|
3
|
+
*
|
|
4
|
+
* Moved out of `backend.ts` unchanged (HOTSPOT-10). The bodies below are byte-identical to the
|
|
5
|
+
* ones that lived there; only the two names `backend.ts` still calls became exports.
|
|
6
|
+
*
|
|
7
|
+
* ⚠ It answers about FORM, never about content: whether the discriminator the mapper switches on
|
|
8
|
+
* is present and carries the field that discriminator promises. That is the same side of the
|
|
9
|
+
* repair boundary as the tool-call validator — the relay never judges what a model said, only
|
|
10
|
+
* whether the shape it arrived in can be read.
|
|
11
|
+
*
|
|
12
|
+
* ⚠ This module imports `isRecord` and nothing else. Keep it that way: it sits beneath the
|
|
13
|
+
* transport it validates for, and a dependency pointing back at `backend.js` would be a cycle.
|
|
14
|
+
*/
|
|
15
|
+
export type ResponseProtocol = "openai-chat" | "anthropic-messages";
|
|
16
|
+
/**
|
|
17
|
+
* Validate only the protocol structure the response mappers rely on. Optional identifiers,
|
|
18
|
+
* model names and usage remain optional because several compatible providers legitimately omit
|
|
19
|
+
* them; the required response discriminator must not be optional, or `{}` becomes a successful
|
|
20
|
+
* empty assistant message.
|
|
21
|
+
*/
|
|
22
|
+
export declare function invalidEnvelopeReason(value: unknown, protocol: ResponseProtocol, streamed: boolean): string | null;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { isRecord } from "../json-shape.js";
|
|
2
|
+
const ANTHROPIC_STREAM_EVENT_FIELDS = new Map([
|
|
3
|
+
["ping", null], ["message_stop", null], ["content_block_stop", null],
|
|
4
|
+
["message_start", "message"], ["content_block_start", "content_block"],
|
|
5
|
+
["content_block_delta", "delta"], ["message_delta", "delta"], ["error", "error"],
|
|
6
|
+
]);
|
|
7
|
+
export function invalidEnvelopeReason(value, protocol, streamed) {
|
|
8
|
+
if (!isRecord(value))
|
|
9
|
+
return "expected a JSON object";
|
|
10
|
+
if (streamed && isRecord(value.error)) {
|
|
11
|
+
const message = typeof value.error.message === "string" ? value.error.message.slice(0, 200) : "";
|
|
12
|
+
return message
|
|
13
|
+
? `stream opened with an in-band error event: ${message}`
|
|
14
|
+
: "stream opened with an in-band error event";
|
|
15
|
+
}
|
|
16
|
+
if (protocol === "openai-chat") {
|
|
17
|
+
if (!Array.isArray(value.choices))
|
|
18
|
+
return "missing choices array";
|
|
19
|
+
if (streamed && value.choices.length === 0) {
|
|
20
|
+
return isRecord(value.usage) ? null : "empty choices without usage";
|
|
21
|
+
}
|
|
22
|
+
if (value.choices.length === 0)
|
|
23
|
+
return "empty choices array";
|
|
24
|
+
for (const rawChoice of value.choices) {
|
|
25
|
+
if (!isRecord(rawChoice))
|
|
26
|
+
return "choice is not an object";
|
|
27
|
+
const message = streamed ? rawChoice.delta : rawChoice.message;
|
|
28
|
+
if (!isRecord(message))
|
|
29
|
+
return streamed ? "choice is missing delta" : "choice is missing message";
|
|
30
|
+
if (!streamed && message.tool_calls !== undefined && message.tool_calls !== null) {
|
|
31
|
+
if (!Array.isArray(message.tool_calls))
|
|
32
|
+
return "message tool_calls is not an array";
|
|
33
|
+
for (const rawCall of message.tool_calls) {
|
|
34
|
+
if (!isRecord(rawCall) || !isRecord(rawCall.function))
|
|
35
|
+
return "invalid tool call";
|
|
36
|
+
if (typeof rawCall.function.name !== "string" || typeof rawCall.function.arguments !== "string") {
|
|
37
|
+
return "invalid tool function";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (!streamed) {
|
|
42
|
+
const hasContent = message.content === null || typeof message.content === "string";
|
|
43
|
+
const hasToolCalls = Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
|
|
44
|
+
if (!hasContent && !hasToolCalls)
|
|
45
|
+
return "message has neither content nor tool calls";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
if (!streamed) {
|
|
51
|
+
if (!Array.isArray(value.content))
|
|
52
|
+
return "missing content array";
|
|
53
|
+
for (const rawBlock of value.content) {
|
|
54
|
+
if (!isRecord(rawBlock) || typeof rawBlock.type !== "string")
|
|
55
|
+
return "invalid content block";
|
|
56
|
+
if (rawBlock.type === "text" && typeof rawBlock.text !== "string")
|
|
57
|
+
return "text block is missing text";
|
|
58
|
+
if (rawBlock.type === "tool_use" && typeof rawBlock.name !== "string")
|
|
59
|
+
return "tool_use block is missing name";
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const field = typeof value.type === "string" ? ANTHROPIC_STREAM_EVENT_FIELDS.get(value.type) : undefined;
|
|
64
|
+
if (field === undefined)
|
|
65
|
+
return "missing or unknown Anthropic event type";
|
|
66
|
+
if (field === null || isRecord(value[field]))
|
|
67
|
+
return null;
|
|
68
|
+
return `${value.type === "error" ? "error event" : String(value.type)} is missing ${field}`;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=envelope-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope-validator.js","sourceRoot":"","sources":["../../src/backend/envelope-validator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Did a provider stream open with a usable response, and what did it say it was?
|
|
3
|
+
*
|
|
4
|
+
* Moved out of `backend.ts` unchanged (HOTSPOT-10). The bodies below are byte-identical to the
|
|
5
|
+
* ones that lived there; only the three names `backend.ts` still calls became exports.
|
|
6
|
+
*
|
|
7
|
+
* Two jobs, kept together because the preflight performs both in one pass over the same bytes:
|
|
8
|
+
*
|
|
9
|
+
* - **Preflight.** Read up to `STREAM_PREFLIGHT_LIMIT` bytes looking for the first data event,
|
|
10
|
+
* validate it, then REPLAY every consumed byte to the consumer. A stream that opens with an
|
|
11
|
+
* in-band error is a dead turn, so failing it here is what lets the candidate walk reach a
|
|
12
|
+
* member that answers instead of spending the pool inside one member's 200.
|
|
13
|
+
* - **Provenance.** Record what the upstream called itself, so a served response can be logged
|
|
14
|
+
* against the model that actually answered rather than the one that was routed to.
|
|
15
|
+
*
|
|
16
|
+
* ⚠ The metadata is process-local, never a wire header. It is declared here because the preflight
|
|
17
|
+
* is what fills it; `backend.ts` keeps the `WeakMap` that binds it to a `Response`.
|
|
18
|
+
*
|
|
19
|
+
* ⚠ Nothing here imports `backend.js`, and nothing may. This module sits beneath the transport it
|
|
20
|
+
* probes for, exactly like `envelope-validator.ts` beside it.
|
|
21
|
+
*/
|
|
22
|
+
import { type ResponseProtocol } from "./envelope-validator.js";
|
|
23
|
+
export interface UpstreamResponseMetadata {
|
|
24
|
+
reportedModel?: string;
|
|
25
|
+
/**
|
|
26
|
+
* How many `tool_use` ids the relay had to mint for this response (`tool-use-ids.ts`). Mutable
|
|
27
|
+
* after the Response exists on purpose: on a stream the pass runs while the body drains, and the
|
|
28
|
+
* server reads this where it reports end-of-stream facts. A count, never an id.
|
|
29
|
+
*/
|
|
30
|
+
toolUseIdRewrites?: number;
|
|
31
|
+
/**
|
|
32
|
+
* How many OUTBOUND tool-call ids the request mapper rewrote to this provider's stated shape
|
|
33
|
+
* (`openai-request.ts`, `compat.toolCallIds: "strict9"`). A count, never an id. Known before
|
|
34
|
+
* egress, so it is set once when the metadata is built.
|
|
35
|
+
*/
|
|
36
|
+
toolCallIdRewrites?: number;
|
|
37
|
+
/**
|
|
38
|
+
* How many replayed tool calls the request mapper stamped with gemini's documented
|
|
39
|
+
* thought-signature sentinel (`openai-request.ts`, `compat.thoughtSignature: "sentinel"`). A
|
|
40
|
+
* count, never a signature. Known before egress, like `toolCallIdRewrites`.
|
|
41
|
+
*
|
|
42
|
+
* Deliberately NOT announced as a response header: this one adds vendor-protocol padding to the
|
|
43
|
+
* relay's own outbound shape and changes nothing about the caller's data, so the operator sees
|
|
44
|
+
* it in the log and the client is told nothing it could act on.
|
|
45
|
+
*/
|
|
46
|
+
thoughtSignatureSentinels?: number;
|
|
47
|
+
}
|
|
48
|
+
type StreamPreflight = {
|
|
49
|
+
ok: true;
|
|
50
|
+
body: ReadableStream<Uint8Array>;
|
|
51
|
+
metadata: UpstreamResponseMetadata;
|
|
52
|
+
} | {
|
|
53
|
+
ok: false;
|
|
54
|
+
reason: string;
|
|
55
|
+
};
|
|
56
|
+
export declare function captureReportedModel(metadata: UpstreamResponseMetadata, value: unknown, protocol: ResponseProtocol, streamed: boolean): void;
|
|
57
|
+
/** Inspect the first data event before handing a provider stream to llm-bridge. */
|
|
58
|
+
export declare function preflightResponseStream(body: ReadableStream<Uint8Array>, protocol: ResponseProtocol): Promise<StreamPreflight>;
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { isRecord } from "../json-shape.js";
|
|
2
|
+
import { STREAM_PREFLIGHT_LIMIT } from "../stream-commit.js";
|
|
3
|
+
import { invalidEnvelopeReason } from "./envelope-validator.js";
|
|
4
|
+
export function captureReportedModel(metadata, value, protocol, streamed) {
|
|
5
|
+
if (metadata.reportedModel !== undefined || !isRecord(value))
|
|
6
|
+
return;
|
|
7
|
+
const envelope = protocol === "anthropic-messages" && streamed && value.type === "message_start"
|
|
8
|
+
? value.message
|
|
9
|
+
: value;
|
|
10
|
+
if (isRecord(envelope) && typeof envelope.model === "string") {
|
|
11
|
+
metadata.reportedModel = envelope.model;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function preflightResponseStream(body, protocol) {
|
|
15
|
+
const reader = body.getReader();
|
|
16
|
+
const chunks = [];
|
|
17
|
+
const decoder = new TextDecoder();
|
|
18
|
+
const metadata = {};
|
|
19
|
+
let buffered = "";
|
|
20
|
+
let byteLength = 0;
|
|
21
|
+
const captureCompleteEvents = (final = false) => {
|
|
22
|
+
let boundary;
|
|
23
|
+
const separator = /\r?\n\r?\n/g;
|
|
24
|
+
while ((boundary = separator.exec(buffered)) !== null) {
|
|
25
|
+
const event = buffered.slice(0, boundary.index);
|
|
26
|
+
buffered = buffered.slice(boundary.index + boundary[0].length);
|
|
27
|
+
separator.lastIndex = 0;
|
|
28
|
+
captureEventModel(event);
|
|
29
|
+
}
|
|
30
|
+
if (final && buffered.trim())
|
|
31
|
+
captureEventModel(buffered);
|
|
32
|
+
};
|
|
33
|
+
const replay = () => {
|
|
34
|
+
let prefixIndex = 0;
|
|
35
|
+
captureCompleteEvents();
|
|
36
|
+
return { ok: true, metadata, body: new ReadableStream({
|
|
37
|
+
async pull(controller) {
|
|
38
|
+
if (prefixIndex < chunks.length) {
|
|
39
|
+
controller.enqueue(chunks[prefixIndex++]);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const more = await reader.read();
|
|
44
|
+
if (more.done) {
|
|
45
|
+
buffered += decoder.decode();
|
|
46
|
+
captureCompleteEvents(true);
|
|
47
|
+
controller.close();
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
buffered += decoder.decode(more.value, { stream: true });
|
|
51
|
+
captureCompleteEvents();
|
|
52
|
+
controller.enqueue(more.value);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
controller.error(error);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
async cancel(reasonToCancel) {
|
|
60
|
+
await reader.cancel(reasonToCancel).catch(() => { });
|
|
61
|
+
},
|
|
62
|
+
}) };
|
|
63
|
+
};
|
|
64
|
+
const fail = async (reason) => {
|
|
65
|
+
await reader.cancel().catch(() => { });
|
|
66
|
+
return { ok: false, reason };
|
|
67
|
+
};
|
|
68
|
+
const inspectEvent = (event) => {
|
|
69
|
+
const data = event
|
|
70
|
+
.split(/\r?\n/)
|
|
71
|
+
.filter((line) => line.startsWith("data:"))
|
|
72
|
+
.map((line) => line.slice(5).trimStart())
|
|
73
|
+
.join("\n")
|
|
74
|
+
.trim();
|
|
75
|
+
if (!data)
|
|
76
|
+
return undefined;
|
|
77
|
+
if (data === "[DONE]")
|
|
78
|
+
return "stream ended before a response event";
|
|
79
|
+
let parsed;
|
|
80
|
+
try {
|
|
81
|
+
parsed = JSON.parse(data);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return "data event is not valid JSON";
|
|
85
|
+
}
|
|
86
|
+
captureReportedModel(metadata, parsed, protocol, true);
|
|
87
|
+
return invalidEnvelopeReason(parsed, protocol, true);
|
|
88
|
+
};
|
|
89
|
+
const captureEventModel = (event) => {
|
|
90
|
+
const data = event
|
|
91
|
+
.split(/\r?\n/)
|
|
92
|
+
.filter((line) => line.startsWith("data:"))
|
|
93
|
+
.map((line) => line.slice(5).trimStart())
|
|
94
|
+
.join("\n")
|
|
95
|
+
.trim();
|
|
96
|
+
if (!data || data === "[DONE]")
|
|
97
|
+
return;
|
|
98
|
+
try {
|
|
99
|
+
captureReportedModel(metadata, JSON.parse(data), protocol, true);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
while (byteLength <= STREAM_PREFLIGHT_LIMIT) {
|
|
105
|
+
const next = await reader.read().catch(() => null);
|
|
106
|
+
if (next === null)
|
|
107
|
+
return fail("stream failed during preflight");
|
|
108
|
+
if (next.done) {
|
|
109
|
+
buffered += decoder.decode();
|
|
110
|
+
let finalReason = buffered.trim() ? inspectEvent(buffered) : undefined;
|
|
111
|
+
if (finalReason === undefined && buffered.trim()) {
|
|
112
|
+
try {
|
|
113
|
+
const parsed = JSON.parse(buffered);
|
|
114
|
+
captureReportedModel(metadata, parsed, protocol, false);
|
|
115
|
+
finalReason = invalidEnvelopeReason(parsed, protocol, false);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (finalReason === null)
|
|
121
|
+
return replay();
|
|
122
|
+
return fail(finalReason ?? "stream ended before a response event");
|
|
123
|
+
}
|
|
124
|
+
chunks.push(next.value);
|
|
125
|
+
byteLength += next.value.byteLength;
|
|
126
|
+
if (byteLength > STREAM_PREFLIGHT_LIMIT)
|
|
127
|
+
return fail("no response event within preflight limit");
|
|
128
|
+
buffered += decoder.decode(next.value, { stream: true });
|
|
129
|
+
let boundary;
|
|
130
|
+
const separator = /\r?\n\r?\n/g;
|
|
131
|
+
while ((boundary = separator.exec(buffered)) !== null) {
|
|
132
|
+
const event = buffered.slice(0, boundary.index);
|
|
133
|
+
buffered = buffered.slice(boundary.index + boundary[0].length);
|
|
134
|
+
separator.lastIndex = 0;
|
|
135
|
+
const reason = inspectEvent(event);
|
|
136
|
+
if (reason === undefined)
|
|
137
|
+
continue;
|
|
138
|
+
if (reason !== null)
|
|
139
|
+
return fail(reason);
|
|
140
|
+
return replay();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return fail("no response event within preflight limit");
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=health-prober.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health-prober.js","sourceRoot":"","sources":["../../src/backend/health-prober.ts"],"names":[],"mappings":"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"}
|
package/dist/backend.js
CHANGED
|
@@ -9,7 +9,8 @@ import { recoverDialectInStream } from "./dialect-stream.js";
|
|
|
9
9
|
import { toolSchemaMap } from "./anthropic.js";
|
|
10
10
|
import { stripOpeningThinkTag, stripThinkTagsInStream } from "./think-tags.js";
|
|
11
11
|
import { knownToolUseIds, rewriteToolUseIds, rewriteToolUseIdsInStream } from "./tool-use-ids.js";
|
|
12
|
-
import {
|
|
12
|
+
import { invalidEnvelopeReason } from "./backend/envelope-validator.js";
|
|
13
|
+
import { captureReportedModel, preflightResponseStream } from "./backend/health-prober.js";
|
|
13
14
|
import { inspectDialectInOpenAiChat, recoverDialectInOpenAiChatStream, } from "./openai-dialect.js";
|
|
14
15
|
import { observeUsage } from "./usage-observer.js";
|
|
15
16
|
export class DialectUnparseableError extends Error {
|
|
@@ -110,215 +111,6 @@ function attachUpstreamMetadata(response, metadata) {
|
|
|
110
111
|
upstreamResponseMetadata.set(response, metadata);
|
|
111
112
|
return response;
|
|
112
113
|
}
|
|
113
|
-
const ANTHROPIC_STREAM_EVENT_FIELDS = new Map([
|
|
114
|
-
["ping", null], ["message_stop", null], ["content_block_stop", null],
|
|
115
|
-
["message_start", "message"], ["content_block_start", "content_block"],
|
|
116
|
-
["content_block_delta", "delta"], ["message_delta", "delta"], ["error", "error"],
|
|
117
|
-
]);
|
|
118
|
-
function invalidEnvelopeReason(value, protocol, streamed) {
|
|
119
|
-
if (!isRecord(value))
|
|
120
|
-
return "expected a JSON object";
|
|
121
|
-
if (streamed && isRecord(value.error)) {
|
|
122
|
-
const message = typeof value.error.message === "string" ? value.error.message.slice(0, 200) : "";
|
|
123
|
-
return message
|
|
124
|
-
? `stream opened with an in-band error event: ${message}`
|
|
125
|
-
: "stream opened with an in-band error event";
|
|
126
|
-
}
|
|
127
|
-
if (protocol === "openai-chat") {
|
|
128
|
-
if (!Array.isArray(value.choices))
|
|
129
|
-
return "missing choices array";
|
|
130
|
-
if (streamed && value.choices.length === 0) {
|
|
131
|
-
return isRecord(value.usage) ? null : "empty choices without usage";
|
|
132
|
-
}
|
|
133
|
-
if (value.choices.length === 0)
|
|
134
|
-
return "empty choices array";
|
|
135
|
-
for (const rawChoice of value.choices) {
|
|
136
|
-
if (!isRecord(rawChoice))
|
|
137
|
-
return "choice is not an object";
|
|
138
|
-
const message = streamed ? rawChoice.delta : rawChoice.message;
|
|
139
|
-
if (!isRecord(message))
|
|
140
|
-
return streamed ? "choice is missing delta" : "choice is missing message";
|
|
141
|
-
if (!streamed && message.tool_calls !== undefined && message.tool_calls !== null) {
|
|
142
|
-
if (!Array.isArray(message.tool_calls))
|
|
143
|
-
return "message tool_calls is not an array";
|
|
144
|
-
for (const rawCall of message.tool_calls) {
|
|
145
|
-
if (!isRecord(rawCall) || !isRecord(rawCall.function))
|
|
146
|
-
return "invalid tool call";
|
|
147
|
-
if (typeof rawCall.function.name !== "string" || typeof rawCall.function.arguments !== "string") {
|
|
148
|
-
return "invalid tool function";
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (!streamed) {
|
|
153
|
-
const hasContent = message.content === null || typeof message.content === "string";
|
|
154
|
-
const hasToolCalls = Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
|
|
155
|
-
if (!hasContent && !hasToolCalls)
|
|
156
|
-
return "message has neither content nor tool calls";
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
if (!streamed) {
|
|
162
|
-
if (!Array.isArray(value.content))
|
|
163
|
-
return "missing content array";
|
|
164
|
-
for (const rawBlock of value.content) {
|
|
165
|
-
if (!isRecord(rawBlock) || typeof rawBlock.type !== "string")
|
|
166
|
-
return "invalid content block";
|
|
167
|
-
if (rawBlock.type === "text" && typeof rawBlock.text !== "string")
|
|
168
|
-
return "text block is missing text";
|
|
169
|
-
if (rawBlock.type === "tool_use" && typeof rawBlock.name !== "string")
|
|
170
|
-
return "tool_use block is missing name";
|
|
171
|
-
}
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
const field = typeof value.type === "string" ? ANTHROPIC_STREAM_EVENT_FIELDS.get(value.type) : undefined;
|
|
175
|
-
if (field === undefined)
|
|
176
|
-
return "missing or unknown Anthropic event type";
|
|
177
|
-
if (field === null || isRecord(value[field]))
|
|
178
|
-
return null;
|
|
179
|
-
return `${value.type === "error" ? "error event" : String(value.type)} is missing ${field}`;
|
|
180
|
-
}
|
|
181
|
-
function captureReportedModel(metadata, value, protocol, streamed) {
|
|
182
|
-
if (metadata.reportedModel !== undefined || !isRecord(value))
|
|
183
|
-
return;
|
|
184
|
-
const envelope = protocol === "anthropic-messages" && streamed && value.type === "message_start"
|
|
185
|
-
? value.message
|
|
186
|
-
: value;
|
|
187
|
-
if (isRecord(envelope) && typeof envelope.model === "string") {
|
|
188
|
-
metadata.reportedModel = envelope.model;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
async function preflightResponseStream(body, protocol) {
|
|
192
|
-
const reader = body.getReader();
|
|
193
|
-
const chunks = [];
|
|
194
|
-
const decoder = new TextDecoder();
|
|
195
|
-
const metadata = {};
|
|
196
|
-
let buffered = "";
|
|
197
|
-
let byteLength = 0;
|
|
198
|
-
const captureCompleteEvents = (final = false) => {
|
|
199
|
-
let boundary;
|
|
200
|
-
const separator = /\r?\n\r?\n/g;
|
|
201
|
-
while ((boundary = separator.exec(buffered)) !== null) {
|
|
202
|
-
const event = buffered.slice(0, boundary.index);
|
|
203
|
-
buffered = buffered.slice(boundary.index + boundary[0].length);
|
|
204
|
-
separator.lastIndex = 0;
|
|
205
|
-
captureEventModel(event);
|
|
206
|
-
}
|
|
207
|
-
if (final && buffered.trim())
|
|
208
|
-
captureEventModel(buffered);
|
|
209
|
-
};
|
|
210
|
-
const replay = () => {
|
|
211
|
-
let prefixIndex = 0;
|
|
212
|
-
captureCompleteEvents();
|
|
213
|
-
return { ok: true, metadata, body: new ReadableStream({
|
|
214
|
-
async pull(controller) {
|
|
215
|
-
if (prefixIndex < chunks.length) {
|
|
216
|
-
controller.enqueue(chunks[prefixIndex++]);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
try {
|
|
220
|
-
const more = await reader.read();
|
|
221
|
-
if (more.done) {
|
|
222
|
-
buffered += decoder.decode();
|
|
223
|
-
captureCompleteEvents(true);
|
|
224
|
-
controller.close();
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
buffered += decoder.decode(more.value, { stream: true });
|
|
228
|
-
captureCompleteEvents();
|
|
229
|
-
controller.enqueue(more.value);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
catch (error) {
|
|
233
|
-
controller.error(error);
|
|
234
|
-
}
|
|
235
|
-
},
|
|
236
|
-
async cancel(reasonToCancel) {
|
|
237
|
-
await reader.cancel(reasonToCancel).catch(() => { });
|
|
238
|
-
},
|
|
239
|
-
}) };
|
|
240
|
-
};
|
|
241
|
-
const fail = async (reason) => {
|
|
242
|
-
await reader.cancel().catch(() => { });
|
|
243
|
-
return { ok: false, reason };
|
|
244
|
-
};
|
|
245
|
-
const inspectEvent = (event) => {
|
|
246
|
-
const data = event
|
|
247
|
-
.split(/\r?\n/)
|
|
248
|
-
.filter((line) => line.startsWith("data:"))
|
|
249
|
-
.map((line) => line.slice(5).trimStart())
|
|
250
|
-
.join("\n")
|
|
251
|
-
.trim();
|
|
252
|
-
if (!data)
|
|
253
|
-
return undefined;
|
|
254
|
-
if (data === "[DONE]")
|
|
255
|
-
return "stream ended before a response event";
|
|
256
|
-
let parsed;
|
|
257
|
-
try {
|
|
258
|
-
parsed = JSON.parse(data);
|
|
259
|
-
}
|
|
260
|
-
catch {
|
|
261
|
-
return "data event is not valid JSON";
|
|
262
|
-
}
|
|
263
|
-
captureReportedModel(metadata, parsed, protocol, true);
|
|
264
|
-
return invalidEnvelopeReason(parsed, protocol, true);
|
|
265
|
-
};
|
|
266
|
-
const captureEventModel = (event) => {
|
|
267
|
-
const data = event
|
|
268
|
-
.split(/\r?\n/)
|
|
269
|
-
.filter((line) => line.startsWith("data:"))
|
|
270
|
-
.map((line) => line.slice(5).trimStart())
|
|
271
|
-
.join("\n")
|
|
272
|
-
.trim();
|
|
273
|
-
if (!data || data === "[DONE]")
|
|
274
|
-
return;
|
|
275
|
-
try {
|
|
276
|
-
captureReportedModel(metadata, JSON.parse(data), protocol, true);
|
|
277
|
-
}
|
|
278
|
-
catch {
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
while (byteLength <= STREAM_PREFLIGHT_LIMIT) {
|
|
282
|
-
const next = await reader.read().catch(() => null);
|
|
283
|
-
if (next === null)
|
|
284
|
-
return fail("stream failed during preflight");
|
|
285
|
-
if (next.done) {
|
|
286
|
-
buffered += decoder.decode();
|
|
287
|
-
let finalReason = buffered.trim() ? inspectEvent(buffered) : undefined;
|
|
288
|
-
if (finalReason === undefined && buffered.trim()) {
|
|
289
|
-
try {
|
|
290
|
-
const parsed = JSON.parse(buffered);
|
|
291
|
-
captureReportedModel(metadata, parsed, protocol, false);
|
|
292
|
-
finalReason = invalidEnvelopeReason(parsed, protocol, false);
|
|
293
|
-
}
|
|
294
|
-
catch {
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
if (finalReason === null)
|
|
298
|
-
return replay();
|
|
299
|
-
return fail(finalReason ?? "stream ended before a response event");
|
|
300
|
-
}
|
|
301
|
-
chunks.push(next.value);
|
|
302
|
-
byteLength += next.value.byteLength;
|
|
303
|
-
if (byteLength > STREAM_PREFLIGHT_LIMIT)
|
|
304
|
-
return fail("no response event within preflight limit");
|
|
305
|
-
buffered += decoder.decode(next.value, { stream: true });
|
|
306
|
-
let boundary;
|
|
307
|
-
const separator = /\r?\n\r?\n/g;
|
|
308
|
-
while ((boundary = separator.exec(buffered)) !== null) {
|
|
309
|
-
const event = buffered.slice(0, boundary.index);
|
|
310
|
-
buffered = buffered.slice(boundary.index + boundary[0].length);
|
|
311
|
-
separator.lastIndex = 0;
|
|
312
|
-
const reason = inspectEvent(event);
|
|
313
|
-
if (reason === undefined)
|
|
314
|
-
continue;
|
|
315
|
-
if (reason !== null)
|
|
316
|
-
return fail(reason);
|
|
317
|
-
return replay();
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return fail("no response event within preflight limit");
|
|
321
|
-
}
|
|
322
114
|
async function fetchAnthropicBackend(attempt, args, invokeFetch) {
|
|
323
115
|
const target = attempt.target;
|
|
324
116
|
const init = { method: args.method, headers: args.anthropicHeaders, signal: args.signal };
|