@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,233 @@
|
|
|
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 { randomUUID } from 'node:crypto';
|
|
33
|
+
/**
|
|
34
|
+
* Adapt an OpenAI SSE chunk iterable into Anthropic stream events.
|
|
35
|
+
* Yields events; on upstream error, the error propagates (caller decides).
|
|
36
|
+
*/
|
|
37
|
+
export async function* adaptOpenAIStream(upstream, opts) {
|
|
38
|
+
const state = {
|
|
39
|
+
messageId: opts.messageId ?? `msg_${randomUUID()}`,
|
|
40
|
+
model: opts.model,
|
|
41
|
+
nextBlockIndex: 0,
|
|
42
|
+
textBlock: null,
|
|
43
|
+
thinkingBlock: null,
|
|
44
|
+
toolCalls: new Map(),
|
|
45
|
+
usage: {
|
|
46
|
+
input_tokens: 0,
|
|
47
|
+
output_tokens: 0,
|
|
48
|
+
cache_creation_input_tokens: null,
|
|
49
|
+
cache_read_input_tokens: null,
|
|
50
|
+
},
|
|
51
|
+
stopReason: null,
|
|
52
|
+
stopSequence: null,
|
|
53
|
+
};
|
|
54
|
+
yield buildMessageStart(state);
|
|
55
|
+
for await (const chunk of upstream) {
|
|
56
|
+
yield* handleChunk(chunk, state);
|
|
57
|
+
}
|
|
58
|
+
// Close any blocks that are still open. Order: thinking → text → tool_use
|
|
59
|
+
// (so consumers see a tidy LIFO close sequence).
|
|
60
|
+
if (state.thinkingBlock) {
|
|
61
|
+
yield { type: 'content_block_stop', index: state.thinkingBlock.index };
|
|
62
|
+
state.thinkingBlock = null;
|
|
63
|
+
}
|
|
64
|
+
if (state.textBlock) {
|
|
65
|
+
yield { type: 'content_block_stop', index: state.textBlock.index };
|
|
66
|
+
state.textBlock = null;
|
|
67
|
+
}
|
|
68
|
+
for (const tc of state.toolCalls.values()) {
|
|
69
|
+
yield { type: 'content_block_stop', index: tc.anthropicIndex };
|
|
70
|
+
}
|
|
71
|
+
state.toolCalls.clear();
|
|
72
|
+
yield {
|
|
73
|
+
type: 'message_delta',
|
|
74
|
+
delta: {
|
|
75
|
+
stop_reason: state.stopReason ?? 'end_turn',
|
|
76
|
+
stop_sequence: state.stopSequence,
|
|
77
|
+
},
|
|
78
|
+
usage: { output_tokens: state.usage.output_tokens },
|
|
79
|
+
};
|
|
80
|
+
yield { type: 'message_stop' };
|
|
81
|
+
}
|
|
82
|
+
function buildMessageStart(state) {
|
|
83
|
+
const message = {
|
|
84
|
+
id: state.messageId,
|
|
85
|
+
type: 'message',
|
|
86
|
+
role: 'assistant',
|
|
87
|
+
model: state.model,
|
|
88
|
+
content: [],
|
|
89
|
+
stop_reason: null,
|
|
90
|
+
stop_sequence: null,
|
|
91
|
+
usage: state.usage,
|
|
92
|
+
};
|
|
93
|
+
return { type: 'message_start', message };
|
|
94
|
+
}
|
|
95
|
+
function* handleChunk(chunk, state) {
|
|
96
|
+
const choice = chunk.choices?.[0];
|
|
97
|
+
if (choice) {
|
|
98
|
+
yield* handleDelta(choice.delta, state);
|
|
99
|
+
if (choice.finish_reason) {
|
|
100
|
+
state.stopReason = mapFinishReason(choice.finish_reason);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (chunk.usage)
|
|
104
|
+
mergeUsage(state.usage, chunk.usage);
|
|
105
|
+
}
|
|
106
|
+
function* handleDelta(delta, state) {
|
|
107
|
+
if (!delta)
|
|
108
|
+
return;
|
|
109
|
+
// Order: reasoning_content first (it always precedes content in DeepSeek-R1),
|
|
110
|
+
// then content, then tool_calls. Within a single chunk all three could be
|
|
111
|
+
// present in theory; we handle them in that natural reading order.
|
|
112
|
+
if (typeof delta.reasoning_content === 'string' && delta.reasoning_content.length > 0) {
|
|
113
|
+
// Closing text is wrong here — DeepSeek-R1 sends ALL reasoning_content
|
|
114
|
+
// BEFORE any content. So a thinking block is opened first and stays open
|
|
115
|
+
// until content arrives. If text was somehow already open, close it.
|
|
116
|
+
if (state.textBlock) {
|
|
117
|
+
yield { type: 'content_block_stop', index: state.textBlock.index };
|
|
118
|
+
state.textBlock = null;
|
|
119
|
+
}
|
|
120
|
+
if (!state.thinkingBlock) {
|
|
121
|
+
const index = state.nextBlockIndex++;
|
|
122
|
+
state.thinkingBlock = { index };
|
|
123
|
+
yield {
|
|
124
|
+
type: 'content_block_start',
|
|
125
|
+
index,
|
|
126
|
+
content_block: { type: 'thinking', thinking: '', signature: '' },
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
yield {
|
|
130
|
+
type: 'content_block_delta',
|
|
131
|
+
index: state.thinkingBlock.index,
|
|
132
|
+
delta: { type: 'thinking_delta', thinking: delta.reasoning_content },
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (typeof delta.content === 'string' && delta.content.length > 0) {
|
|
136
|
+
// First real text → close any thinking block (it's done by definition).
|
|
137
|
+
if (state.thinkingBlock) {
|
|
138
|
+
yield { type: 'content_block_stop', index: state.thinkingBlock.index };
|
|
139
|
+
state.thinkingBlock = null;
|
|
140
|
+
}
|
|
141
|
+
if (!state.textBlock) {
|
|
142
|
+
const index = state.nextBlockIndex++;
|
|
143
|
+
state.textBlock = { index };
|
|
144
|
+
yield {
|
|
145
|
+
type: 'content_block_start',
|
|
146
|
+
index,
|
|
147
|
+
content_block: { type: 'text', text: '', citations: null },
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
yield {
|
|
151
|
+
type: 'content_block_delta',
|
|
152
|
+
index: state.textBlock.index,
|
|
153
|
+
delta: { type: 'text_delta', text: delta.content },
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (delta.tool_calls && delta.tool_calls.length > 0) {
|
|
157
|
+
// Tool calls always come AFTER text/thinking in practice. Close those first.
|
|
158
|
+
if (state.thinkingBlock) {
|
|
159
|
+
yield { type: 'content_block_stop', index: state.thinkingBlock.index };
|
|
160
|
+
state.thinkingBlock = null;
|
|
161
|
+
}
|
|
162
|
+
if (state.textBlock) {
|
|
163
|
+
yield { type: 'content_block_stop', index: state.textBlock.index };
|
|
164
|
+
state.textBlock = null;
|
|
165
|
+
}
|
|
166
|
+
for (const tc of delta.tool_calls) {
|
|
167
|
+
const openaiIndex = tc.index;
|
|
168
|
+
let entry = state.toolCalls.get(openaiIndex);
|
|
169
|
+
if (!entry) {
|
|
170
|
+
// First time we're seeing this tool call. Allocate an Anthropic block.
|
|
171
|
+
const anthropicIndex = state.nextBlockIndex++;
|
|
172
|
+
entry = {
|
|
173
|
+
anthropicIndex,
|
|
174
|
+
id: tc.id ?? `toolu_${randomUUID().replace(/-/g, '').slice(0, 22)}`,
|
|
175
|
+
nameEmitted: false,
|
|
176
|
+
};
|
|
177
|
+
state.toolCalls.set(openaiIndex, entry);
|
|
178
|
+
// We may not have a name yet on the very first delta; if not, defer
|
|
179
|
+
// emitting content_block_start until we do. (Anthropic's content_block
|
|
180
|
+
// requires `name` to be set at start time.)
|
|
181
|
+
}
|
|
182
|
+
// If we know the name now and haven't emitted start yet, do so.
|
|
183
|
+
const name = tc.function?.name;
|
|
184
|
+
if (!entry.nameEmitted && name) {
|
|
185
|
+
entry.nameEmitted = true;
|
|
186
|
+
yield {
|
|
187
|
+
type: 'content_block_start',
|
|
188
|
+
index: entry.anthropicIndex,
|
|
189
|
+
content_block: {
|
|
190
|
+
type: 'tool_use',
|
|
191
|
+
id: entry.id,
|
|
192
|
+
name,
|
|
193
|
+
input: {},
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
const args = tc.function?.arguments;
|
|
198
|
+
if (entry.nameEmitted && typeof args === 'string' && args.length > 0) {
|
|
199
|
+
yield {
|
|
200
|
+
type: 'content_block_delta',
|
|
201
|
+
index: entry.anthropicIndex,
|
|
202
|
+
delta: { type: 'input_json_delta', partial_json: args },
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function mapFinishReason(reason) {
|
|
209
|
+
switch (reason) {
|
|
210
|
+
case 'stop':
|
|
211
|
+
return 'end_turn';
|
|
212
|
+
case 'length':
|
|
213
|
+
return 'max_tokens';
|
|
214
|
+
case 'tool_calls':
|
|
215
|
+
case 'function_call':
|
|
216
|
+
return 'tool_use';
|
|
217
|
+
case 'content_filter':
|
|
218
|
+
// No exact Anthropic equivalent. 'stop_sequence' is the closest semantic
|
|
219
|
+
// ("model was stopped externally") but it's a stretch. We use 'end_turn'
|
|
220
|
+
// and rely on telemetry to flag filtered outputs upstream.
|
|
221
|
+
return 'end_turn';
|
|
222
|
+
default:
|
|
223
|
+
return 'end_turn';
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function mergeUsage(target, src) {
|
|
227
|
+
target.input_tokens = src.prompt_tokens;
|
|
228
|
+
target.output_tokens = src.completion_tokens;
|
|
229
|
+
if (src.prompt_tokens_details?.cached_tokens !== undefined) {
|
|
230
|
+
target.cache_read_input_tokens = src.prompt_tokens_details.cached_tokens;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=streamAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamAdapter.js","sourceRoot":"","sources":["../../../src/providers/openai-compat/streamAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI chat/completions wire types — a small subset of the v1 schema, just
|
|
3
|
+
* enough to send a streaming request and decode the SSE responses. We don't
|
|
4
|
+
* pull in `openai` as a dependency because we want to support arbitrary
|
|
5
|
+
* compatible servers (Ollama, Groq, OpenRouter, LM Studio, self-hosted),
|
|
6
|
+
* not all of which match the official SDK's strict shape.
|
|
7
|
+
*
|
|
8
|
+
* Reference: https://platform.openai.com/docs/api-reference/chat/streaming
|
|
9
|
+
*/
|
|
10
|
+
export interface OpenAIToolFunction {
|
|
11
|
+
name: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
parameters?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export interface OpenAIToolDef {
|
|
16
|
+
type: 'function';
|
|
17
|
+
function: OpenAIToolFunction;
|
|
18
|
+
}
|
|
19
|
+
export type OpenAIToolChoice = 'auto' | 'none' | 'required' | {
|
|
20
|
+
type: 'function';
|
|
21
|
+
function: {
|
|
22
|
+
name: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export interface OpenAIContentPartText {
|
|
26
|
+
type: 'text';
|
|
27
|
+
text: string;
|
|
28
|
+
}
|
|
29
|
+
export interface OpenAIContentPartImage {
|
|
30
|
+
type: 'image_url';
|
|
31
|
+
image_url: {
|
|
32
|
+
url: string;
|
|
33
|
+
detail?: 'auto' | 'low' | 'high';
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export type OpenAIContentPart = OpenAIContentPartText | OpenAIContentPartImage;
|
|
37
|
+
export interface OpenAIToolCall {
|
|
38
|
+
/** Stable identifier set on first delta, used to match `tool` role results. */
|
|
39
|
+
id: string;
|
|
40
|
+
type: 'function';
|
|
41
|
+
function: {
|
|
42
|
+
name: string;
|
|
43
|
+
arguments: string;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export interface OpenAIMessageSystem {
|
|
47
|
+
role: 'system';
|
|
48
|
+
content: string;
|
|
49
|
+
}
|
|
50
|
+
export interface OpenAIMessageUser {
|
|
51
|
+
role: 'user';
|
|
52
|
+
content: string | OpenAIContentPart[];
|
|
53
|
+
}
|
|
54
|
+
export interface OpenAIMessageAssistant {
|
|
55
|
+
role: 'assistant';
|
|
56
|
+
content?: string | null;
|
|
57
|
+
tool_calls?: OpenAIToolCall[];
|
|
58
|
+
}
|
|
59
|
+
export interface OpenAIMessageTool {
|
|
60
|
+
role: 'tool';
|
|
61
|
+
tool_call_id: string;
|
|
62
|
+
content: string;
|
|
63
|
+
}
|
|
64
|
+
export type OpenAIMessage = OpenAIMessageSystem | OpenAIMessageUser | OpenAIMessageAssistant | OpenAIMessageTool;
|
|
65
|
+
export interface OpenAIRequest {
|
|
66
|
+
model: string;
|
|
67
|
+
messages: OpenAIMessage[];
|
|
68
|
+
tools?: OpenAIToolDef[];
|
|
69
|
+
tool_choice?: OpenAIToolChoice;
|
|
70
|
+
stream: true;
|
|
71
|
+
stream_options?: {
|
|
72
|
+
include_usage?: boolean;
|
|
73
|
+
};
|
|
74
|
+
max_tokens?: number;
|
|
75
|
+
temperature?: number;
|
|
76
|
+
top_p?: number;
|
|
77
|
+
stop?: string | string[];
|
|
78
|
+
user?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Some providers (DeepSeek, Together) accept extra fields here without
|
|
81
|
+
* complaining; we pass them through as an escape hatch.
|
|
82
|
+
*/
|
|
83
|
+
[key: string]: unknown;
|
|
84
|
+
}
|
|
85
|
+
export interface OpenAIDeltaToolCall {
|
|
86
|
+
/** Index in the streamed assistant message's tool_calls array. */
|
|
87
|
+
index: number;
|
|
88
|
+
/** Set on the first chunk that introduces this tool call. */
|
|
89
|
+
id?: string;
|
|
90
|
+
type?: 'function';
|
|
91
|
+
function?: {
|
|
92
|
+
name?: string;
|
|
93
|
+
arguments?: string;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export interface OpenAIStreamDelta {
|
|
97
|
+
role?: 'assistant';
|
|
98
|
+
content?: string | null;
|
|
99
|
+
tool_calls?: OpenAIDeltaToolCall[];
|
|
100
|
+
/** Some providers (DeepSeek-R1) emit reasoning as a sibling field. */
|
|
101
|
+
reasoning_content?: string | null;
|
|
102
|
+
}
|
|
103
|
+
export type OpenAIFinishReason = 'stop' | 'length' | 'tool_calls' | 'content_filter' | 'function_call' | null;
|
|
104
|
+
export interface OpenAIStreamChoice {
|
|
105
|
+
index: number;
|
|
106
|
+
delta: OpenAIStreamDelta;
|
|
107
|
+
finish_reason: OpenAIFinishReason;
|
|
108
|
+
}
|
|
109
|
+
export interface OpenAIUsage {
|
|
110
|
+
prompt_tokens: number;
|
|
111
|
+
completion_tokens: number;
|
|
112
|
+
total_tokens: number;
|
|
113
|
+
prompt_tokens_details?: {
|
|
114
|
+
cached_tokens?: number;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export interface OpenAIStreamChunk {
|
|
118
|
+
id: string;
|
|
119
|
+
object: 'chat.completion.chunk';
|
|
120
|
+
created: number;
|
|
121
|
+
model: string;
|
|
122
|
+
choices: OpenAIStreamChoice[];
|
|
123
|
+
/** Only present when stream_options.include_usage = true and only on the final chunk. */
|
|
124
|
+
usage?: OpenAIUsage;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/openai-compat/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI chat/completions wire types — a small subset of the v1 schema, just
|
|
3
|
+
* enough to send a streaming request and decode the SSE responses. We don't
|
|
4
|
+
* pull in `openai` as a dependency because we want to support arbitrary
|
|
5
|
+
* compatible servers (Ollama, Groq, OpenRouter, LM Studio, self-hosted),
|
|
6
|
+
* not all of which match the official SDK's strict shape.
|
|
7
|
+
*
|
|
8
|
+
* Reference: https://platform.openai.com/docs/api-reference/chat/streaming
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/providers/openai-compat/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider registry / selector.
|
|
3
|
+
*
|
|
4
|
+
* The selector keeps a map of registered providers and picks one by id, by
|
|
5
|
+
* default rule, or by a fallback chain. It does NOT instantiate providers
|
|
6
|
+
* itself — callers register pre-built instances. This keeps the selector
|
|
7
|
+
* dependency-light and makes testing easy.
|
|
8
|
+
*
|
|
9
|
+
* Selection precedence at lookup time:
|
|
10
|
+
* 1. Explicit id passed to pick()
|
|
11
|
+
* 2. Per-process default (set via setDefault)
|
|
12
|
+
* 3. First provider in the registry that passes healthCheck
|
|
13
|
+
*/
|
|
14
|
+
import type { Logger } from '@bbclaw/shared';
|
|
15
|
+
import type { Provider } from './types.js';
|
|
16
|
+
export interface ProviderRegistryOptions {
|
|
17
|
+
logger?: Logger;
|
|
18
|
+
}
|
|
19
|
+
export declare class ProviderRegistry {
|
|
20
|
+
private readonly providers;
|
|
21
|
+
private defaultId;
|
|
22
|
+
private readonly logger;
|
|
23
|
+
constructor(opts?: ProviderRegistryOptions);
|
|
24
|
+
register(provider: Provider): this;
|
|
25
|
+
unregister(id: string): boolean;
|
|
26
|
+
has(id: string): boolean;
|
|
27
|
+
get(id: string): Provider | undefined;
|
|
28
|
+
list(): Provider[];
|
|
29
|
+
setDefault(id: string): void;
|
|
30
|
+
getDefault(): Provider | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Pick a provider. Lookup order:
|
|
33
|
+
* - explicit id
|
|
34
|
+
* - default
|
|
35
|
+
* - first registered (deterministic — Map preserves insertion order)
|
|
36
|
+
* Throws if the registry is empty or the requested id doesn't exist.
|
|
37
|
+
*/
|
|
38
|
+
pick(id?: string): Provider;
|
|
39
|
+
/**
|
|
40
|
+
* Probe every registered provider in parallel and return the results.
|
|
41
|
+
* Useful for the `/provider health` UX and for surfacing config issues at
|
|
42
|
+
* startup.
|
|
43
|
+
*/
|
|
44
|
+
healthCheckAll(): Promise<Array<{
|
|
45
|
+
id: string;
|
|
46
|
+
ok: boolean;
|
|
47
|
+
latencyMs?: number;
|
|
48
|
+
reason?: string;
|
|
49
|
+
}>>;
|
|
50
|
+
closeAll(): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=selector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector.d.ts","sourceRoot":"","sources":["../../src/providers/selector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAE1C,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;IACxD,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;gBAEnB,IAAI,GAAE,uBAA4B;IAK9C,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IASlC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAM/B,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIxB,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAIrC,IAAI,IAAI,QAAQ,EAAE;IAIlB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAO5B,UAAU,IAAI,QAAQ,GAAG,SAAS;IAIlC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,QAAQ;IAa3B;;;;OAIG;IACG,cAAc,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAclG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAWhC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider registry / selector.
|
|
3
|
+
*
|
|
4
|
+
* The selector keeps a map of registered providers and picks one by id, by
|
|
5
|
+
* default rule, or by a fallback chain. It does NOT instantiate providers
|
|
6
|
+
* itself — callers register pre-built instances. This keeps the selector
|
|
7
|
+
* dependency-light and makes testing easy.
|
|
8
|
+
*
|
|
9
|
+
* Selection precedence at lookup time:
|
|
10
|
+
* 1. Explicit id passed to pick()
|
|
11
|
+
* 2. Per-process default (set via setDefault)
|
|
12
|
+
* 3. First provider in the registry that passes healthCheck
|
|
13
|
+
*/
|
|
14
|
+
export class ProviderRegistry {
|
|
15
|
+
providers = new Map();
|
|
16
|
+
defaultId = null;
|
|
17
|
+
logger;
|
|
18
|
+
constructor(opts = {}) {
|
|
19
|
+
this.logger =
|
|
20
|
+
opts.logger ?? { debug: () => { }, info: () => { }, warn: () => { }, error: () => { } };
|
|
21
|
+
}
|
|
22
|
+
register(provider) {
|
|
23
|
+
if (this.providers.has(provider.id)) {
|
|
24
|
+
throw new Error(`Provider already registered with id "${provider.id}"`);
|
|
25
|
+
}
|
|
26
|
+
this.providers.set(provider.id, provider);
|
|
27
|
+
if (this.defaultId === null)
|
|
28
|
+
this.defaultId = provider.id;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
unregister(id) {
|
|
32
|
+
const removed = this.providers.delete(id);
|
|
33
|
+
if (this.defaultId === id)
|
|
34
|
+
this.defaultId = this.providers.keys().next().value ?? null;
|
|
35
|
+
return removed;
|
|
36
|
+
}
|
|
37
|
+
has(id) {
|
|
38
|
+
return this.providers.has(id);
|
|
39
|
+
}
|
|
40
|
+
get(id) {
|
|
41
|
+
return this.providers.get(id);
|
|
42
|
+
}
|
|
43
|
+
list() {
|
|
44
|
+
return [...this.providers.values()];
|
|
45
|
+
}
|
|
46
|
+
setDefault(id) {
|
|
47
|
+
if (!this.providers.has(id)) {
|
|
48
|
+
throw new Error(`Cannot set default to unknown provider id "${id}"`);
|
|
49
|
+
}
|
|
50
|
+
this.defaultId = id;
|
|
51
|
+
}
|
|
52
|
+
getDefault() {
|
|
53
|
+
return this.defaultId ? this.providers.get(this.defaultId) : undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Pick a provider. Lookup order:
|
|
57
|
+
* - explicit id
|
|
58
|
+
* - default
|
|
59
|
+
* - first registered (deterministic — Map preserves insertion order)
|
|
60
|
+
* Throws if the registry is empty or the requested id doesn't exist.
|
|
61
|
+
*/
|
|
62
|
+
pick(id) {
|
|
63
|
+
if (id) {
|
|
64
|
+
const p = this.providers.get(id);
|
|
65
|
+
if (!p)
|
|
66
|
+
throw new Error(`No provider registered with id "${id}"`);
|
|
67
|
+
return p;
|
|
68
|
+
}
|
|
69
|
+
const def = this.getDefault();
|
|
70
|
+
if (def)
|
|
71
|
+
return def;
|
|
72
|
+
const first = this.providers.values().next().value;
|
|
73
|
+
if (!first)
|
|
74
|
+
throw new Error('ProviderRegistry is empty');
|
|
75
|
+
return first;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Probe every registered provider in parallel and return the results.
|
|
79
|
+
* Useful for the `/provider health` UX and for surfacing config issues at
|
|
80
|
+
* startup.
|
|
81
|
+
*/
|
|
82
|
+
async healthCheckAll() {
|
|
83
|
+
const entries = [...this.providers.values()];
|
|
84
|
+
const results = await Promise.all(entries.map(async (p) => {
|
|
85
|
+
const r = await p.healthCheck().catch((e) => ({
|
|
86
|
+
ok: false,
|
|
87
|
+
reason: e instanceof Error ? e.message : String(e),
|
|
88
|
+
}));
|
|
89
|
+
return { id: p.id, ...r };
|
|
90
|
+
}));
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
async closeAll() {
|
|
94
|
+
await Promise.all(this.list().map((p) => p.close().catch((e) => {
|
|
95
|
+
this.logger.warn(`[registry] close failed for ${p.id}:`, e);
|
|
96
|
+
})));
|
|
97
|
+
this.providers.clear();
|
|
98
|
+
this.defaultId = null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=selector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector.js","sourceRoot":"","sources":["../../src/providers/selector.ts"],"names":[],"mappings":"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"}
|