@bbclaw/core 0.1.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/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/anthropic/index.d.ts +44 -0
- package/dist/providers/anthropic/index.d.ts.map +1 -0
- package/dist/providers/anthropic/index.js +75 -0
- package/dist/providers/anthropic/index.js.map +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai-compat/index.d.ts +59 -0
- package/dist/providers/openai-compat/index.d.ts.map +1 -0
- package/dist/providers/openai-compat/index.js +175 -0
- package/dist/providers/openai-compat/index.js.map +1 -0
- package/dist/providers/openai-compat/presets.d.ts +22 -0
- package/dist/providers/openai-compat/presets.d.ts.map +1 -0
- package/dist/providers/openai-compat/presets.js +73 -0
- package/dist/providers/openai-compat/presets.js.map +1 -0
- package/dist/providers/openai-compat/requestTranslate.d.ts +39 -0
- package/dist/providers/openai-compat/requestTranslate.d.ts.map +1 -0
- package/dist/providers/openai-compat/requestTranslate.js +228 -0
- package/dist/providers/openai-compat/requestTranslate.js.map +1 -0
- package/dist/providers/openai-compat/sseParser.d.ts +29 -0
- package/dist/providers/openai-compat/sseParser.d.ts.map +1 -0
- package/dist/providers/openai-compat/sseParser.js +139 -0
- package/dist/providers/openai-compat/sseParser.js.map +1 -0
- package/dist/providers/openai-compat/streamAdapter.d.ts +45 -0
- package/dist/providers/openai-compat/streamAdapter.d.ts.map +1 -0
- package/dist/providers/openai-compat/streamAdapter.js +233 -0
- package/dist/providers/openai-compat/streamAdapter.js.map +1 -0
- package/dist/providers/openai-compat/types.d.ts +126 -0
- package/dist/providers/openai-compat/types.d.ts.map +1 -0
- package/dist/providers/openai-compat/types.js +11 -0
- package/dist/providers/openai-compat/types.js.map +1 -0
- package/dist/providers/selector.d.ts +52 -0
- package/dist/providers/selector.d.ts.map +1 -0
- package/dist/providers/selector.js +101 -0
- package/dist/providers/selector.js.map +1 -0
- package/dist/providers/types.d.ts +114 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +152 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/providers/web-bridge/deepseek/spec.d.ts +22 -0
- package/dist/providers/web-bridge/deepseek/spec.d.ts.map +1 -0
- package/dist/providers/web-bridge/deepseek/spec.js +70 -0
- package/dist/providers/web-bridge/deepseek/spec.js.map +1 -0
- package/dist/providers/web-bridge/historySerializer.d.ts +49 -0
- package/dist/providers/web-bridge/historySerializer.d.ts.map +1 -0
- package/dist/providers/web-bridge/historySerializer.js +152 -0
- package/dist/providers/web-bridge/historySerializer.js.map +1 -0
- package/dist/providers/web-bridge/index.d.ts +10 -0
- package/dist/providers/web-bridge/index.d.ts.map +1 -0
- package/dist/providers/web-bridge/index.js +10 -0
- package/dist/providers/web-bridge/index.js.map +1 -0
- package/dist/providers/web-bridge/promptInjector.d.ts +63 -0
- package/dist/providers/web-bridge/promptInjector.d.ts.map +1 -0
- package/dist/providers/web-bridge/promptInjector.js +189 -0
- package/dist/providers/web-bridge/promptInjector.js.map +1 -0
- package/dist/providers/web-bridge/provider.d.ts +59 -0
- package/dist/providers/web-bridge/provider.d.ts.map +1 -0
- package/dist/providers/web-bridge/provider.js +176 -0
- package/dist/providers/web-bridge/provider.js.map +1 -0
- package/dist/providers/web-bridge/shared/BrowserSession.d.ts +51 -0
- package/dist/providers/web-bridge/shared/BrowserSession.d.ts.map +1 -0
- package/dist/providers/web-bridge/shared/BrowserSession.js +88 -0
- package/dist/providers/web-bridge/shared/BrowserSession.js.map +1 -0
- package/dist/providers/web-bridge/shared/WebBridgeAdapter.d.ts +97 -0
- package/dist/providers/web-bridge/shared/WebBridgeAdapter.d.ts.map +1 -0
- package/dist/providers/web-bridge/shared/WebBridgeAdapter.js +359 -0
- package/dist/providers/web-bridge/shared/WebBridgeAdapter.js.map +1 -0
- package/dist/providers/web-bridge/shared/observerScript.d.ts +41 -0
- package/dist/providers/web-bridge/shared/observerScript.d.ts.map +1 -0
- package/dist/providers/web-bridge/shared/observerScript.js +138 -0
- package/dist/providers/web-bridge/shared/observerScript.js.map +1 -0
- package/dist/providers/web-bridge/shared/types.d.ts +94 -0
- package/dist/providers/web-bridge/shared/types.d.ts.map +1 -0
- package/dist/providers/web-bridge/shared/types.js +25 -0
- package/dist/providers/web-bridge/shared/types.js.map +1 -0
- package/dist/providers/web-bridge/toolUseParser.d.ts +70 -0
- package/dist/providers/web-bridge/toolUseParser.d.ts.map +1 -0
- package/dist/providers/web-bridge/toolUseParser.js +360 -0
- package/dist/providers/web-bridge/toolUseParser.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translate an Anthropic-style request to OpenAI chat/completions wire format.
|
|
3
|
+
*
|
|
4
|
+
* Mapping decisions documented inline. The big ones:
|
|
5
|
+
*
|
|
6
|
+
* - Anthropic `system` is a TextBlockParam[] (each block can carry cache_control
|
|
7
|
+
* hints). OpenAI has no equivalent; we concatenate the blocks with \n\n and
|
|
8
|
+
* drop cache hints. The caller can strip them upstream if it wants stronger
|
|
9
|
+
* guarantees (e.g. removing redundant boundary markers).
|
|
10
|
+
*
|
|
11
|
+
* - Anthropic tool_use/tool_result are content blocks inside a message.
|
|
12
|
+
* OpenAI splits them: assistant messages carry `tool_calls`, and tool
|
|
13
|
+
* results live in a separate `role: tool` message keyed by tool_call_id.
|
|
14
|
+
* We expand one Anthropic message into 1+N OpenAI messages as needed.
|
|
15
|
+
*
|
|
16
|
+
* - Anthropic input is structured JSON (already an object). OpenAI sends it
|
|
17
|
+
* serialized as a string in `function.arguments`. We JSON.stringify on the
|
|
18
|
+
* way out and JSON.parse on the way back (in streamAdapter).
|
|
19
|
+
*
|
|
20
|
+
* - Thinking blocks are model-private CoT — they have no business in the
|
|
21
|
+
* request side, so we drop them silently. (Some providers like DeepSeek-R1
|
|
22
|
+
* echo reasoning back via `reasoning_content`; we'd render that on the
|
|
23
|
+
* response side, not the request side.)
|
|
24
|
+
*
|
|
25
|
+
* - Images: Anthropic accepts base64 `source` or URL; OpenAI accepts URL or
|
|
26
|
+
* base64 data URI. We convert base64 sources into `data:` URIs.
|
|
27
|
+
*/
|
|
28
|
+
export function translateRequest(params, opts = {}) {
|
|
29
|
+
const model = opts.modelMap ? opts.modelMap(params.model) : params.model;
|
|
30
|
+
const messages = [];
|
|
31
|
+
if (params.system) {
|
|
32
|
+
messages.push({ role: 'system', content: systemBlocksToText(params.system) });
|
|
33
|
+
}
|
|
34
|
+
for (const m of params.messages) {
|
|
35
|
+
messages.push(...translateMessage(m));
|
|
36
|
+
}
|
|
37
|
+
const req = {
|
|
38
|
+
model,
|
|
39
|
+
messages,
|
|
40
|
+
stream: true,
|
|
41
|
+
max_tokens: params.max_tokens,
|
|
42
|
+
};
|
|
43
|
+
if (typeof params.temperature === 'number')
|
|
44
|
+
req.temperature = params.temperature;
|
|
45
|
+
if (typeof params.top_p === 'number')
|
|
46
|
+
req.top_p = params.top_p;
|
|
47
|
+
if (params.stop_sequences && params.stop_sequences.length > 0)
|
|
48
|
+
req.stop = params.stop_sequences;
|
|
49
|
+
if (params.tools && params.tools.length > 0) {
|
|
50
|
+
req.tools = params.tools.map(translateTool).filter((t) => t !== null);
|
|
51
|
+
}
|
|
52
|
+
if (params.tool_choice) {
|
|
53
|
+
const tc = translateToolChoice(params.tool_choice);
|
|
54
|
+
if (tc)
|
|
55
|
+
req.tool_choice = tc;
|
|
56
|
+
}
|
|
57
|
+
if (params.metadata?.user_id)
|
|
58
|
+
req.user = params.metadata.user_id;
|
|
59
|
+
if (opts.includeUsage !== false) {
|
|
60
|
+
req.stream_options = { include_usage: true };
|
|
61
|
+
}
|
|
62
|
+
if (opts.extraBody)
|
|
63
|
+
Object.assign(req, opts.extraBody);
|
|
64
|
+
return req;
|
|
65
|
+
}
|
|
66
|
+
// ----- system blocks -----
|
|
67
|
+
function systemBlocksToText(system) {
|
|
68
|
+
if (typeof system === 'string')
|
|
69
|
+
return system;
|
|
70
|
+
return system.map((b) => b.text).join('\n\n');
|
|
71
|
+
}
|
|
72
|
+
// ----- tool definitions -----
|
|
73
|
+
function translateTool(tool) {
|
|
74
|
+
// We only know how to translate "custom" function tools. Anthropic's built-in
|
|
75
|
+
// tools (computer_20250124, bash_20250124, text_editor_20250124) have no
|
|
76
|
+
// OpenAI equivalent and would silently no-op if forwarded — better to drop
|
|
77
|
+
// and let the caller see they're missing than to confuse the model.
|
|
78
|
+
if (typeof tool !== 'object' || tool === null)
|
|
79
|
+
return null;
|
|
80
|
+
const t = tool;
|
|
81
|
+
if (!('name' in t) || !('input_schema' in t))
|
|
82
|
+
return null;
|
|
83
|
+
return {
|
|
84
|
+
type: 'function',
|
|
85
|
+
function: {
|
|
86
|
+
name: t.name,
|
|
87
|
+
description: t.description,
|
|
88
|
+
parameters: t.input_schema,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function translateToolChoice(choice) {
|
|
93
|
+
switch (choice.type) {
|
|
94
|
+
case 'auto':
|
|
95
|
+
return 'auto';
|
|
96
|
+
case 'any':
|
|
97
|
+
return 'required';
|
|
98
|
+
case 'none':
|
|
99
|
+
return 'none';
|
|
100
|
+
case 'tool':
|
|
101
|
+
return { type: 'function', function: { name: choice.name } };
|
|
102
|
+
default:
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// ----- messages -----
|
|
107
|
+
function translateMessage(m) {
|
|
108
|
+
const content = m.content;
|
|
109
|
+
if (typeof content === 'string') {
|
|
110
|
+
// Trivial case — plain text message.
|
|
111
|
+
return [m.role === 'user' ? { role: 'user', content } : { role: 'assistant', content }];
|
|
112
|
+
}
|
|
113
|
+
if (m.role === 'user') {
|
|
114
|
+
return translateUserBlocks(content);
|
|
115
|
+
}
|
|
116
|
+
return translateAssistantBlocks(content);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* User messages can contain a mix of text, images, and tool_result blocks.
|
|
120
|
+
* Tool results MUST become standalone `role: tool` messages in OpenAI's
|
|
121
|
+
* schema; the remaining text/images stay as a single user message.
|
|
122
|
+
*/
|
|
123
|
+
function translateUserBlocks(blocks) {
|
|
124
|
+
const out = [];
|
|
125
|
+
const parts = [];
|
|
126
|
+
for (const b of blocks) {
|
|
127
|
+
if (b.type === 'tool_result') {
|
|
128
|
+
// Flush accumulated parts first so message ordering is preserved.
|
|
129
|
+
if (parts.length > 0) {
|
|
130
|
+
out.push({ role: 'user', content: parts.splice(0) });
|
|
131
|
+
}
|
|
132
|
+
out.push(toolResultToMessage(b));
|
|
133
|
+
}
|
|
134
|
+
else if (b.type === 'text') {
|
|
135
|
+
parts.push({ type: 'text', text: b.text });
|
|
136
|
+
}
|
|
137
|
+
else if (b.type === 'image') {
|
|
138
|
+
const url = imageToDataUrl(b);
|
|
139
|
+
if (url)
|
|
140
|
+
parts.push({ type: 'image_url', image_url: { url } });
|
|
141
|
+
}
|
|
142
|
+
else if (b.type === 'document') {
|
|
143
|
+
// PDFs / structured docs — OpenAI's vision endpoints handle these
|
|
144
|
+
// inconsistently. For now we render as a text fence so the model at
|
|
145
|
+
// least sees the marker; downstream callers can substitute richer
|
|
146
|
+
// formats when they know the provider supports them.
|
|
147
|
+
parts.push({ type: 'text', text: '[document attachment omitted in openai-compat]' });
|
|
148
|
+
}
|
|
149
|
+
// Other block types (thinking on user side shouldn't happen) — ignore.
|
|
150
|
+
}
|
|
151
|
+
if (parts.length > 0) {
|
|
152
|
+
out.push({
|
|
153
|
+
role: 'user',
|
|
154
|
+
content: parts.length === 1 && parts[0]?.type === 'text' ? parts[0].text : parts,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
function toolResultToMessage(b) {
|
|
160
|
+
let text;
|
|
161
|
+
if (typeof b.content === 'string') {
|
|
162
|
+
text = b.content;
|
|
163
|
+
}
|
|
164
|
+
else if (Array.isArray(b.content)) {
|
|
165
|
+
text = b.content
|
|
166
|
+
.map((p) => {
|
|
167
|
+
if (p.type === 'text')
|
|
168
|
+
return p.text;
|
|
169
|
+
if (p.type === 'image')
|
|
170
|
+
return '[image]';
|
|
171
|
+
return '';
|
|
172
|
+
})
|
|
173
|
+
.join('\n');
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
text = '';
|
|
177
|
+
}
|
|
178
|
+
if (b.is_error)
|
|
179
|
+
text = `[error] ${text}`;
|
|
180
|
+
return { role: 'tool', tool_call_id: b.tool_use_id, content: text };
|
|
181
|
+
}
|
|
182
|
+
function imageToDataUrl(b) {
|
|
183
|
+
const src = b.source;
|
|
184
|
+
if (src.type === 'base64') {
|
|
185
|
+
return `data:${src.media_type};base64,${src.data}`;
|
|
186
|
+
}
|
|
187
|
+
if (src.type === 'url') {
|
|
188
|
+
return src.url;
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Assistant messages can contain text and tool_use blocks. OpenAI's assistant
|
|
194
|
+
* message has both `content` (text) and `tool_calls` (parallel array of calls),
|
|
195
|
+
* so we collapse them into a single message.
|
|
196
|
+
*/
|
|
197
|
+
function translateAssistantBlocks(blocks) {
|
|
198
|
+
const textParts = [];
|
|
199
|
+
const toolCalls = [];
|
|
200
|
+
for (const b of blocks) {
|
|
201
|
+
if (b.type === 'text') {
|
|
202
|
+
textParts.push(b.text);
|
|
203
|
+
}
|
|
204
|
+
else if (b.type === 'tool_use') {
|
|
205
|
+
const tu = b;
|
|
206
|
+
toolCalls.push({
|
|
207
|
+
id: tu.id,
|
|
208
|
+
type: 'function',
|
|
209
|
+
function: {
|
|
210
|
+
name: tu.name,
|
|
211
|
+
arguments: JSON.stringify(tu.input ?? {}),
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
// thinking/redacted_thinking — drop on the request side.
|
|
216
|
+
}
|
|
217
|
+
const msg = { role: 'assistant' };
|
|
218
|
+
if (textParts.length > 0)
|
|
219
|
+
msg.content = textParts.join('');
|
|
220
|
+
if (toolCalls.length > 0)
|
|
221
|
+
msg.tool_calls = toolCalls;
|
|
222
|
+
// OpenAI requires either content or tool_calls; if somehow neither, send empty content.
|
|
223
|
+
if (msg.content === undefined && (!msg.tool_calls || msg.tool_calls.length === 0)) {
|
|
224
|
+
msg.content = '';
|
|
225
|
+
}
|
|
226
|
+
return [msg];
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=requestTranslate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestTranslate.js","sourceRoot":"","sources":["../../../src/providers/openai-compat/requestTranslate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE (Server-Sent Events) decoder for the OpenAI chat/completions endpoint.
|
|
3
|
+
*
|
|
4
|
+
* SSE framing per WHATWG: events are separated by blank lines. Within an
|
|
5
|
+
* event, lines starting with `data:` carry the payload (multiple `data:`
|
|
6
|
+
* lines are joined with \n). OpenAI uses the simplified single-line variant
|
|
7
|
+
* and signals end-of-stream with a literal `data: [DONE]` line.
|
|
8
|
+
*
|
|
9
|
+
* We intentionally don't pull a generic SSE library — the full spec includes
|
|
10
|
+
* named events / id / retry, none of which OpenAI uses, and the buffering
|
|
11
|
+
* logic for our subset is ~30 lines.
|
|
12
|
+
*/
|
|
13
|
+
import type { OpenAIStreamChunk } from './types.js';
|
|
14
|
+
export declare class SSEParseError extends Error {
|
|
15
|
+
readonly raw: string;
|
|
16
|
+
constructor(message: string, raw: string);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read an SSE stream from a Response body, yielding parsed OpenAI chunks.
|
|
20
|
+
*
|
|
21
|
+
* Behavior:
|
|
22
|
+
* - Skips comment lines (`:` prefix).
|
|
23
|
+
* - Skips non-`data:` lines silently (event/id/retry).
|
|
24
|
+
* - Yields when a complete event boundary (\n\n or \r\n\r\n) is reached.
|
|
25
|
+
* - Stops on `data: [DONE]`.
|
|
26
|
+
* - Throws on stream-level errors (network, body close mid-frame).
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseSSEStream(body: ReadableStream<Uint8Array>): AsyncIterable<OpenAIStreamChunk>;
|
|
29
|
+
//# sourceMappingURL=sseParser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sseParser.d.ts","sourceRoot":"","sources":["../../../src/providers/openai-compat/sseParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;gBACR,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;CAKzC;AAED;;;;;;;;;GASG;AACH,wBAAuB,cAAc,CACnC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAC/B,aAAa,CAAC,iBAAiB,CAAC,CAoClC"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE (Server-Sent Events) decoder for the OpenAI chat/completions endpoint.
|
|
3
|
+
*
|
|
4
|
+
* SSE framing per WHATWG: events are separated by blank lines. Within an
|
|
5
|
+
* event, lines starting with `data:` carry the payload (multiple `data:`
|
|
6
|
+
* lines are joined with \n). OpenAI uses the simplified single-line variant
|
|
7
|
+
* and signals end-of-stream with a literal `data: [DONE]` line.
|
|
8
|
+
*
|
|
9
|
+
* We intentionally don't pull a generic SSE library — the full spec includes
|
|
10
|
+
* named events / id / retry, none of which OpenAI uses, and the buffering
|
|
11
|
+
* logic for our subset is ~30 lines.
|
|
12
|
+
*/
|
|
13
|
+
export class SSEParseError extends Error {
|
|
14
|
+
raw;
|
|
15
|
+
constructor(message, raw) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'SSEParseError';
|
|
18
|
+
this.raw = raw;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Read an SSE stream from a Response body, yielding parsed OpenAI chunks.
|
|
23
|
+
*
|
|
24
|
+
* Behavior:
|
|
25
|
+
* - Skips comment lines (`:` prefix).
|
|
26
|
+
* - Skips non-`data:` lines silently (event/id/retry).
|
|
27
|
+
* - Yields when a complete event boundary (\n\n or \r\n\r\n) is reached.
|
|
28
|
+
* - Stops on `data: [DONE]`.
|
|
29
|
+
* - Throws on stream-level errors (network, body close mid-frame).
|
|
30
|
+
*/
|
|
31
|
+
export async function* parseSSEStream(body) {
|
|
32
|
+
const reader = body.getReader();
|
|
33
|
+
const decoder = new TextDecoder('utf-8');
|
|
34
|
+
let buffer = '';
|
|
35
|
+
try {
|
|
36
|
+
while (true) {
|
|
37
|
+
const { done, value } = await reader.read();
|
|
38
|
+
if (done) {
|
|
39
|
+
// Flush any trailing data — usually empty after [DONE], but defensive.
|
|
40
|
+
const flushed = drainEvents(buffer, true);
|
|
41
|
+
for (const ev of flushed.events) {
|
|
42
|
+
const chunk = decodeEvent(ev);
|
|
43
|
+
if (chunk === 'DONE')
|
|
44
|
+
return;
|
|
45
|
+
if (chunk)
|
|
46
|
+
yield chunk;
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
buffer += decoder.decode(value, { stream: true });
|
|
51
|
+
const { events, remaining } = drainEvents(buffer, false);
|
|
52
|
+
buffer = remaining;
|
|
53
|
+
for (const ev of events) {
|
|
54
|
+
const chunk = decodeEvent(ev);
|
|
55
|
+
if (chunk === 'DONE')
|
|
56
|
+
return;
|
|
57
|
+
if (chunk)
|
|
58
|
+
yield chunk;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
try {
|
|
64
|
+
reader.releaseLock();
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// ignore
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Pull complete events out of the buffer. An event ends at \n\n or \r\n\r\n.
|
|
73
|
+
* If `final` is true, treat any non-empty trailing content as a last event.
|
|
74
|
+
*/
|
|
75
|
+
function drainEvents(buffer, final) {
|
|
76
|
+
const events = [];
|
|
77
|
+
let cursor = 0;
|
|
78
|
+
while (cursor < buffer.length) {
|
|
79
|
+
const lf2 = buffer.indexOf('\n\n', cursor);
|
|
80
|
+
const crlf2 = buffer.indexOf('\r\n\r\n', cursor);
|
|
81
|
+
let end = -1;
|
|
82
|
+
let sepLen = 0;
|
|
83
|
+
if (lf2 !== -1 && (crlf2 === -1 || lf2 < crlf2)) {
|
|
84
|
+
end = lf2;
|
|
85
|
+
sepLen = 2;
|
|
86
|
+
}
|
|
87
|
+
else if (crlf2 !== -1) {
|
|
88
|
+
end = crlf2;
|
|
89
|
+
sepLen = 4;
|
|
90
|
+
}
|
|
91
|
+
if (end === -1)
|
|
92
|
+
break;
|
|
93
|
+
const event = buffer.slice(cursor, end);
|
|
94
|
+
if (event.trim().length > 0)
|
|
95
|
+
events.push(event);
|
|
96
|
+
cursor = end + sepLen;
|
|
97
|
+
}
|
|
98
|
+
let remaining = buffer.slice(cursor);
|
|
99
|
+
if (final && remaining.trim().length > 0) {
|
|
100
|
+
events.push(remaining);
|
|
101
|
+
remaining = '';
|
|
102
|
+
}
|
|
103
|
+
return { events, remaining };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Decode a single event block. Returns:
|
|
107
|
+
* - 'DONE' for the [DONE] sentinel
|
|
108
|
+
* - parsed chunk
|
|
109
|
+
* - null for events that aren't useful (e.g. comment-only)
|
|
110
|
+
* Throws SSEParseError on malformed JSON in a `data:` line.
|
|
111
|
+
*/
|
|
112
|
+
function decodeEvent(rawEvent) {
|
|
113
|
+
const lines = rawEvent.split(/\r?\n/);
|
|
114
|
+
const dataLines = [];
|
|
115
|
+
for (const line of lines) {
|
|
116
|
+
if (line.startsWith(':'))
|
|
117
|
+
continue; // comment
|
|
118
|
+
if (line.startsWith('data:')) {
|
|
119
|
+
// Per SSE spec, `data:` is followed by a single optional space then payload.
|
|
120
|
+
const payload = line.slice(5);
|
|
121
|
+
dataLines.push(payload.startsWith(' ') ? payload.slice(1) : payload);
|
|
122
|
+
}
|
|
123
|
+
// Non-data fields (event:, id:, retry:) — OpenAI doesn't use them, ignore.
|
|
124
|
+
}
|
|
125
|
+
if (dataLines.length === 0)
|
|
126
|
+
return null;
|
|
127
|
+
const data = dataLines.join('\n').trim();
|
|
128
|
+
if (data === '[DONE]')
|
|
129
|
+
return 'DONE';
|
|
130
|
+
if (data.length === 0)
|
|
131
|
+
return null;
|
|
132
|
+
try {
|
|
133
|
+
return JSON.parse(data);
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
throw new SSEParseError(`Failed to parse SSE event JSON: ${e instanceof Error ? e.message : String(e)}`, data);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=sseParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sseParser.js","sourceRoot":"","sources":["../../../src/providers/openai-compat/sseParser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stream adapter: OpenAI chat/completions SSE → Anthropic RawMessageStreamEvent.
|
|
3
|
+
*
|
|
4
|
+
* This is the trickiest piece in the openai-compat provider. The two protocols
|
|
5
|
+
* model the world differently:
|
|
6
|
+
*
|
|
7
|
+
* - OpenAI streams a single "assistant message" whose `content` and
|
|
8
|
+
* `tool_calls` are filled in incrementally via deltas. Tool call deltas
|
|
9
|
+
* carry an `index` that identifies WHICH tool call in the parallel array
|
|
10
|
+
* they belong to.
|
|
11
|
+
*
|
|
12
|
+
* - Anthropic models the same response as a sequence of typed CONTENT BLOCKS
|
|
13
|
+
* (text / tool_use / thinking), each with its own index and lifecycle:
|
|
14
|
+
* content_block_start → content_block_delta* → content_block_stop.
|
|
15
|
+
*
|
|
16
|
+
* The mapping rules we use:
|
|
17
|
+
* • `delta.content` → text block, opened lazily, closed when something else starts
|
|
18
|
+
* • `delta.reasoning_content` (DeepSeek-R1 dialect) → thinking block, same lifecycle
|
|
19
|
+
* • `delta.tool_calls[i]` → one tool_use block per OpenAI index `i`
|
|
20
|
+
* • final `usage` → message_delta.usage
|
|
21
|
+
* • `finish_reason` → message_delta.stop_reason via mapStopReason()
|
|
22
|
+
*
|
|
23
|
+
* Block index allocation: we increment a single counter as we open blocks.
|
|
24
|
+
* The OpenAI-side `tool_calls.index` is REMAPPED to an Anthropic block index
|
|
25
|
+
* (they're not the same number — Anthropic counts ALL blocks including text).
|
|
26
|
+
*
|
|
27
|
+
* Concurrency: in practice OpenAI streams tool calls sequentially (one finishes
|
|
28
|
+
* before the next starts), but the spec allows parallel. We handle both cases
|
|
29
|
+
* by keeping per-OpenAI-index state and only closing when we see clear evidence
|
|
30
|
+
* the block is done (finish_reason or end of stream).
|
|
31
|
+
*/
|
|
32
|
+
import type { RawMessageStreamEvent } from '@anthropic-ai/sdk/resources/messages/messages.js';
|
|
33
|
+
import type { OpenAIStreamChunk } from './types.js';
|
|
34
|
+
export interface AdapterOptions {
|
|
35
|
+
/** Used in the synthesized message_start event. */
|
|
36
|
+
model: string;
|
|
37
|
+
/** When set, override the message id (otherwise we generate `msg_<uuid>`). */
|
|
38
|
+
messageId?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Adapt an OpenAI SSE chunk iterable into Anthropic stream events.
|
|
42
|
+
* Yields events; on upstream error, the error propagates (caller decides).
|
|
43
|
+
*/
|
|
44
|
+
export declare function adaptOpenAIStream(upstream: AsyncIterable<OpenAIStreamChunk>, opts: AdapterOptions): AsyncIterable<RawMessageStreamEvent>;
|
|
45
|
+
//# sourceMappingURL=streamAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamAdapter.d.ts","sourceRoot":"","sources":["../../../src/providers/openai-compat/streamAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,OAAO,KAAK,EAEV,qBAAqB,EAGtB,MAAM,kDAAkD,CAAA;AACzD,OAAO,KAAK,EAEV,iBAAiB,EAElB,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAkBD;;;GAGG;AACH,wBAAuB,iBAAiB,CACtC,QAAQ,EAAE,aAAa,CAAC,iBAAiB,CAAC,EAC1C,IAAI,EAAE,cAAc,GACnB,aAAa,CAAC,qBAAqB,CAAC,CAgDtC"}
|