@qorejs/qore 0.7.2 → 0.9.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/README.md +294 -5
- package/dist/src/core/owner.d.ts +14 -0
- package/dist/src/core/owner.js +66 -0
- package/dist/src/core/response-runtime.js +5 -5
- package/dist/src/core/response-state.d.ts +4 -0
- package/dist/src/core/response-state.js +67 -2
- package/dist/src/core/signal-context.js +19 -9
- package/dist/src/core/signal-nodes.d.ts +5 -0
- package/dist/src/core/signal-nodes.js +51 -21
- package/dist/src/core/signal-types.d.ts +2 -0
- package/dist/src/core/signal.d.ts +1 -0
- package/dist/src/core/signal.js +2 -0
- package/dist/src/core/stream-types.d.ts +12 -0
- package/dist/src/core/stream.d.ts +1 -1
- package/dist/src/core/stream.js +144 -0
- package/dist/src/dom/app.js +2 -3
- package/dist/src/dom/dom.d.ts +1 -0
- package/dist/src/dom/dom.js +96 -9
- package/dist/src/dom/scope.d.ts +3 -1
- package/dist/src/dom/scope.js +10 -3
- package/dist/src/index.d.ts +10 -4
- package/dist/src/index.js +7 -2
- package/dist/src/providers/anthropic.js +6 -4
- package/dist/src/providers/deepseek.d.ts +2 -0
- package/dist/src/providers/deepseek.js +90 -0
- package/dist/src/providers/line-adapter.d.ts +2 -0
- package/dist/src/providers/line-adapter.js +83 -0
- package/dist/src/providers/line-parser.d.ts +5 -0
- package/dist/src/providers/line-parser.js +103 -0
- package/dist/src/providers/metadata.d.ts +8 -0
- package/dist/src/providers/metadata.js +193 -0
- package/dist/src/providers/ollama.d.ts +2 -0
- package/dist/src/providers/ollama.js +82 -0
- package/dist/src/providers/openai.js +6 -4
- package/dist/src/providers/openrouter.d.ts +2 -0
- package/dist/src/providers/openrouter.js +90 -0
- package/dist/src/providers/sse-adapter.js +129 -44
- package/dist/src/providers/sse-parser.d.ts +1 -1
- package/dist/src/providers/sse-parser.js +74 -38
- package/dist/src/providers/sse.d.ts +7 -1
- package/dist/src/providers/sse.js +6 -0
- package/dist/src/providers/types.d.ts +145 -0
- package/dist/src/server/sse-response.d.ts +16 -0
- package/dist/src/server/sse-response.js +102 -0
- package/dist/src/shared/utils.d.ts +1 -0
- package/dist/src/shared/utils.js +8 -1
- package/package.json +9 -2
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { createSSEAdapter, readEnv } from './sse.js';
|
|
2
|
+
const DEFAULT_BASE_URL = 'https://openrouter.ai/api/v1';
|
|
3
|
+
const DEFAULT_MODEL = 'openai/gpt-4.1-mini';
|
|
4
|
+
function normalizeChatInput(input) {
|
|
5
|
+
if (typeof input === 'string') {
|
|
6
|
+
return [{ role: 'user', content: input }];
|
|
7
|
+
}
|
|
8
|
+
if (Array.isArray(input)) {
|
|
9
|
+
return input;
|
|
10
|
+
}
|
|
11
|
+
if (input && typeof input === 'object' && 'role' in input) {
|
|
12
|
+
return [input];
|
|
13
|
+
}
|
|
14
|
+
return input;
|
|
15
|
+
}
|
|
16
|
+
export function createOpenRouter(options = {}) {
|
|
17
|
+
const { apiKey, baseURL = DEFAULT_BASE_URL, model = DEFAULT_MODEL, headers: defaultHeaders = {}, fetch: fetchImpl = globalThis.fetch, retry } = options;
|
|
18
|
+
const resolvedApiKey = apiKey ?? readEnv('OPENROUTER_API_KEY');
|
|
19
|
+
if (!resolvedApiKey) {
|
|
20
|
+
throw new Error('Qore OpenRouter adapter requires an API key. Pass apiKey or set OPENROUTER_API_KEY.');
|
|
21
|
+
}
|
|
22
|
+
const transport = createSSEAdapter({
|
|
23
|
+
name: 'OpenRouter',
|
|
24
|
+
url: `${baseURL}/chat/completions`,
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${resolvedApiKey}`,
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
...defaultHeaders
|
|
29
|
+
},
|
|
30
|
+
fetch: fetchImpl,
|
|
31
|
+
...(retry ? { retry } : {}),
|
|
32
|
+
buildRequest(request, requestOptions = {}) {
|
|
33
|
+
const { signal, headers = {}, retry: _retry, ...overrides } = requestOptions;
|
|
34
|
+
const config = {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers,
|
|
37
|
+
body: JSON.stringify({
|
|
38
|
+
model,
|
|
39
|
+
stream: true,
|
|
40
|
+
...request,
|
|
41
|
+
...overrides
|
|
42
|
+
})
|
|
43
|
+
};
|
|
44
|
+
if (signal) {
|
|
45
|
+
config.signal = signal;
|
|
46
|
+
}
|
|
47
|
+
return config;
|
|
48
|
+
},
|
|
49
|
+
parse: (data) => JSON.parse(data),
|
|
50
|
+
eventToText: (event) => {
|
|
51
|
+
const nextChoice = event.data?.choices?.[0];
|
|
52
|
+
return typeof nextChoice?.delta?.content === 'string'
|
|
53
|
+
? nextChoice.delta.content
|
|
54
|
+
: undefined;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
async function* streamEvents(request, requestOptions = {}) {
|
|
58
|
+
for await (const event of transport.stream(request, requestOptions)) {
|
|
59
|
+
yield event.data;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function* streamText(input, requestOptions = {}) {
|
|
63
|
+
const request = input && typeof input === 'object' && 'messages' in input
|
|
64
|
+
? input
|
|
65
|
+
: { messages: input };
|
|
66
|
+
for await (const chunk of transport.streamText(request, requestOptions)) {
|
|
67
|
+
yield chunk;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
chatCompletions: {
|
|
72
|
+
stream: streamEvents
|
|
73
|
+
},
|
|
74
|
+
streamText(input, requestOptions = {}) {
|
|
75
|
+
return streamText(input, requestOptions);
|
|
76
|
+
},
|
|
77
|
+
chat(input, requestOptions = {}) {
|
|
78
|
+
const { signal, headers, retry, ...rest } = requestOptions;
|
|
79
|
+
const request = { ...rest };
|
|
80
|
+
if (!('messages' in request)) {
|
|
81
|
+
request.messages = normalizeChatInput(input);
|
|
82
|
+
}
|
|
83
|
+
return streamText(request, {
|
|
84
|
+
...(signal ? { signal } : {}),
|
|
85
|
+
...(headers ? { headers } : {}),
|
|
86
|
+
...(retry ? { retry } : {})
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -1,62 +1,147 @@
|
|
|
1
|
-
import { normalizeError } from '../shared/utils.js';
|
|
1
|
+
import { normalizeAbortReason, normalizeError, sleep } from '../shared/utils.js';
|
|
2
2
|
import { mergeHeaders, readErrorBody } from './sse-env.js';
|
|
3
3
|
import { getErrorMessage, isErrorEvent, parseEventData, readSSE } from './sse-parser.js';
|
|
4
4
|
function isRequestConfig(value) {
|
|
5
5
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
6
6
|
}
|
|
7
|
+
function toRetryableProviderError(error) {
|
|
8
|
+
return normalizeError(error);
|
|
9
|
+
}
|
|
10
|
+
function withStatus(error, status) {
|
|
11
|
+
const nextError = error;
|
|
12
|
+
nextError.status = status;
|
|
13
|
+
return nextError;
|
|
14
|
+
}
|
|
15
|
+
function normalizeRetryOptions(options, overrides) {
|
|
16
|
+
return {
|
|
17
|
+
maxAttempts: overrides?.maxAttempts ?? options?.maxAttempts ?? 1,
|
|
18
|
+
backoff: overrides?.backoff ?? options?.backoff ?? 'exponential',
|
|
19
|
+
resume: overrides?.resume ?? options?.resume ?? true,
|
|
20
|
+
retryOn: overrides?.retryOn ?? options?.retryOn
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function shouldRetry(error, attempt, options) {
|
|
24
|
+
if (attempt >= options.maxAttempts) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (typeof options.retryOn === 'function') {
|
|
28
|
+
return options.retryOn(error, attempt);
|
|
29
|
+
}
|
|
30
|
+
if (typeof error.status === 'number') {
|
|
31
|
+
return error.status === 408
|
|
32
|
+
|| error.status === 409
|
|
33
|
+
|| error.status === 425
|
|
34
|
+
|| error.status === 429
|
|
35
|
+
|| error.status >= 500;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
async function resolveRetryDelay(options, attempt, error, retryHint) {
|
|
40
|
+
if (retryHint !== null && retryHint >= 0) {
|
|
41
|
+
return retryHint;
|
|
42
|
+
}
|
|
43
|
+
const { backoff = 'exponential' } = options;
|
|
44
|
+
if (typeof backoff === 'function') {
|
|
45
|
+
return Math.max(0, await backoff(attempt, error, retryHint));
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(backoff)) {
|
|
48
|
+
return Math.max(0, backoff[Math.min(attempt - 1, backoff.length - 1)] ?? 0);
|
|
49
|
+
}
|
|
50
|
+
if (backoff === 'exponential') {
|
|
51
|
+
return 250 * 2 ** (attempt - 1);
|
|
52
|
+
}
|
|
53
|
+
return Math.max(0, backoff);
|
|
54
|
+
}
|
|
7
55
|
// Expose a generic SSE adapter so Qore can integrate with any token-streaming endpoint.
|
|
8
56
|
export function createSSEAdapter(options = {}) {
|
|
9
|
-
const { name = 'SSE', url: defaultURL, method: defaultMethod = 'POST', headers: defaultHeaders = {}, fetch: fetchImpl = globalThis.fetch, buildRequest = null, buildChatRequest = null, parse = parseEventData, isError = isErrorEvent, getError = getErrorMessage, eventToText = (event) => typeof event.data === 'string' ? event.data : undefined } = options;
|
|
57
|
+
const { name = 'SSE', url: defaultURL, method: defaultMethod = 'POST', headers: defaultHeaders = {}, fetch: fetchImpl = globalThis.fetch, buildRequest = null, buildChatRequest = null, parse = parseEventData, isError = isErrorEvent, getError = getErrorMessage, eventToText = (event) => typeof event.data === 'string' ? event.data : undefined, retry: defaultRetry } = options;
|
|
10
58
|
if (typeof fetchImpl !== 'function') {
|
|
11
59
|
throw new Error(`Qore ${name} adapter requires fetch in the current runtime`);
|
|
12
60
|
}
|
|
13
61
|
async function* stream(request = {}, requestOptions = {}) {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const signal = requestSignal ?? overrideSignal;
|
|
23
|
-
if (!url) {
|
|
24
|
-
throw new Error(`Qore ${name} adapter requires a request URL`);
|
|
25
|
-
}
|
|
26
|
-
const initConfig = {
|
|
27
|
-
method,
|
|
28
|
-
headers: mergeHeaders(defaultHeaders, mergeHeaders(requestHeaders, overrideHeaders)),
|
|
29
|
-
...init
|
|
30
|
-
};
|
|
31
|
-
if (signal) {
|
|
32
|
-
initConfig.signal = signal;
|
|
33
|
-
}
|
|
34
|
-
const response = await fetchImpl(url, initConfig);
|
|
35
|
-
if (!response.ok) {
|
|
36
|
-
throw new Error(await readErrorBody(response));
|
|
37
|
-
}
|
|
38
|
-
if (!response.body) {
|
|
39
|
-
throw new Error(`${name} streaming response did not include a readable body`);
|
|
40
|
-
}
|
|
41
|
-
for await (const rawEvent of readSSE(response.body)) {
|
|
42
|
-
if (!rawEvent.data || rawEvent.data === '[DONE]') {
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
let parsedEvent;
|
|
62
|
+
const { signal: overrideSignal, headers: overrideHeaders = {}, retry: overrideRetry } = requestOptions;
|
|
63
|
+
const retry = normalizeRetryOptions(defaultRetry, overrideRetry);
|
|
64
|
+
let attempt = 0;
|
|
65
|
+
let lastEventId = null;
|
|
66
|
+
let retryHint = null;
|
|
67
|
+
let activeSignal = overrideSignal ?? null;
|
|
68
|
+
while (true) {
|
|
69
|
+
attempt += 1;
|
|
46
70
|
try {
|
|
47
|
-
|
|
71
|
+
const builtRequest = buildRequest
|
|
72
|
+
? await buildRequest(request, requestOptions)
|
|
73
|
+
: request;
|
|
74
|
+
const requestConfig = isRequestConfig(builtRequest)
|
|
75
|
+
? builtRequest
|
|
76
|
+
: { body: builtRequest };
|
|
77
|
+
const { url = defaultURL, method = defaultMethod, headers: requestHeaders = {}, signal: requestSignal, ...init } = requestConfig;
|
|
78
|
+
const signal = requestSignal ?? overrideSignal;
|
|
79
|
+
activeSignal = signal ?? null;
|
|
80
|
+
if (!url) {
|
|
81
|
+
throw new Error(`Qore ${name} adapter requires a request URL`);
|
|
82
|
+
}
|
|
83
|
+
if (signal?.aborted) {
|
|
84
|
+
throw normalizeAbortReason(signal.reason, `${name} request aborted`);
|
|
85
|
+
}
|
|
86
|
+
const resumeHeaders = retry.resume && lastEventId
|
|
87
|
+
? { 'Last-Event-ID': lastEventId }
|
|
88
|
+
: {};
|
|
89
|
+
const initConfig = {
|
|
90
|
+
method,
|
|
91
|
+
headers: mergeHeaders(defaultHeaders, mergeHeaders(requestHeaders, mergeHeaders(overrideHeaders, resumeHeaders))),
|
|
92
|
+
...init
|
|
93
|
+
};
|
|
94
|
+
if (signal) {
|
|
95
|
+
initConfig.signal = signal;
|
|
96
|
+
}
|
|
97
|
+
const response = await fetchImpl(url, initConfig);
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
throw withStatus(new Error(await readErrorBody(response)), response.status);
|
|
100
|
+
}
|
|
101
|
+
if (!response.body) {
|
|
102
|
+
throw new Error(`${name} streaming response did not include a readable body`);
|
|
103
|
+
}
|
|
104
|
+
for await (const rawEvent of readSSE(response.body, signal)) {
|
|
105
|
+
if (rawEvent.id) {
|
|
106
|
+
lastEventId = rawEvent.id;
|
|
107
|
+
}
|
|
108
|
+
if (rawEvent.retry !== null) {
|
|
109
|
+
retryHint = rawEvent.retry;
|
|
110
|
+
}
|
|
111
|
+
if (!rawEvent.data || rawEvent.data === '[DONE]') {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
let parsedEvent;
|
|
115
|
+
try {
|
|
116
|
+
parsedEvent = await parse(rawEvent.data, rawEvent);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
throw normalizeError(error);
|
|
120
|
+
}
|
|
121
|
+
const nextEvent = {
|
|
122
|
+
...rawEvent,
|
|
123
|
+
data: parsedEvent
|
|
124
|
+
};
|
|
125
|
+
if (await isError(nextEvent)) {
|
|
126
|
+
throw new Error(await getError(nextEvent, name));
|
|
127
|
+
}
|
|
128
|
+
yield nextEvent;
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
48
131
|
}
|
|
49
132
|
catch (error) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
133
|
+
const normalizedError = toRetryableProviderError(error);
|
|
134
|
+
if (activeSignal?.aborted || overrideSignal?.aborted) {
|
|
135
|
+
throw normalizedError;
|
|
136
|
+
}
|
|
137
|
+
if (!await shouldRetry(normalizedError, attempt, retry)) {
|
|
138
|
+
throw normalizedError;
|
|
139
|
+
}
|
|
140
|
+
const delay = await resolveRetryDelay(retry, attempt, normalizedError, retryHint);
|
|
141
|
+
if (delay > 0) {
|
|
142
|
+
await sleep(delay, overrideSignal ?? null);
|
|
143
|
+
}
|
|
58
144
|
}
|
|
59
|
-
yield nextEvent;
|
|
60
145
|
}
|
|
61
146
|
}
|
|
62
147
|
async function* streamText(request = {}, requestOptions = {}) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SSEEvent } from './types.js';
|
|
2
|
-
export declare function readSSE(body: ReadableStream<Uint8Array
|
|
2
|
+
export declare function readSSE(body: ReadableStream<Uint8Array>, signal?: AbortSignal | null): AsyncIterable<SSEEvent<string>>;
|
|
3
3
|
export declare function parseEventData(data: string): unknown;
|
|
4
4
|
export declare function isErrorEvent(event: SSEEvent<unknown>): boolean;
|
|
5
5
|
export declare function getErrorMessage(event: SSEEvent<unknown>, name: string): string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { normalizeAbortReason } from '../shared/utils.js';
|
|
1
2
|
// Parse server-sent events without adding any transport dependency.
|
|
2
|
-
export async function* readSSE(body) {
|
|
3
|
+
export async function* readSSE(body, signal) {
|
|
3
4
|
const reader = body.getReader();
|
|
4
5
|
const decoder = new TextDecoder();
|
|
5
6
|
let buffer = '';
|
|
@@ -7,6 +8,12 @@ export async function* readSSE(body) {
|
|
|
7
8
|
let eventId = null;
|
|
8
9
|
let retry = null;
|
|
9
10
|
let data = [];
|
|
11
|
+
const abortMessage = 'SSE stream aborted';
|
|
12
|
+
const cancelReader = () => {
|
|
13
|
+
void reader.cancel(signal?.reason).catch(() => {
|
|
14
|
+
// Ignore reader cancellation races during abort handling.
|
|
15
|
+
});
|
|
16
|
+
};
|
|
10
17
|
const flushEvent = () => {
|
|
11
18
|
if (data.length === 0) {
|
|
12
19
|
eventName = 'message';
|
|
@@ -26,51 +33,80 @@ export async function* readSSE(body) {
|
|
|
26
33
|
data = [];
|
|
27
34
|
return nextEvent;
|
|
28
35
|
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
if (signal?.aborted) {
|
|
37
|
+
cancelReader();
|
|
38
|
+
throw normalizeAbortReason(signal.reason, abortMessage);
|
|
39
|
+
}
|
|
40
|
+
signal?.addEventListener('abort', cancelReader, { once: true });
|
|
41
|
+
try {
|
|
42
|
+
while (true) {
|
|
43
|
+
if (signal?.aborted) {
|
|
44
|
+
throw normalizeAbortReason(signal.reason, abortMessage);
|
|
45
|
+
}
|
|
46
|
+
let chunk;
|
|
47
|
+
try {
|
|
48
|
+
chunk = await reader.read();
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (signal?.aborted) {
|
|
52
|
+
throw normalizeAbortReason(signal.reason, abortMessage);
|
|
39
53
|
}
|
|
40
|
-
|
|
54
|
+
throw error;
|
|
41
55
|
}
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
const { value, done } = chunk;
|
|
57
|
+
buffer += decoder.decode(value ?? new Uint8Array(), { stream: !done });
|
|
58
|
+
const lines = buffer.split(/\r?\n/);
|
|
59
|
+
buffer = lines.pop() ?? '';
|
|
60
|
+
for (const line of lines) {
|
|
61
|
+
if (!line) {
|
|
62
|
+
const nextEvent = flushEvent();
|
|
63
|
+
if (nextEvent) {
|
|
64
|
+
yield nextEvent;
|
|
65
|
+
}
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (line.startsWith(':')) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const separator = line.indexOf(':');
|
|
72
|
+
const field = separator === -1 ? line : line.slice(0, separator);
|
|
73
|
+
const rawValue = separator === -1 ? '' : line.slice(separator + 1).replace(/^ /, '');
|
|
74
|
+
switch (field) {
|
|
75
|
+
case 'event':
|
|
76
|
+
eventName = rawValue || 'message';
|
|
77
|
+
break;
|
|
78
|
+
case 'data':
|
|
79
|
+
data.push(rawValue);
|
|
80
|
+
break;
|
|
81
|
+
case 'id':
|
|
82
|
+
eventId = rawValue;
|
|
83
|
+
break;
|
|
84
|
+
case 'retry': {
|
|
85
|
+
const parsedRetry = Number.parseInt(rawValue, 10);
|
|
86
|
+
retry = Number.isNaN(parsedRetry) ? null : parsedRetry;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
default:
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
44
92
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
switch (field) {
|
|
49
|
-
case 'event':
|
|
50
|
-
eventName = rawValue || 'message';
|
|
51
|
-
break;
|
|
52
|
-
case 'data':
|
|
53
|
-
data.push(rawValue);
|
|
54
|
-
break;
|
|
55
|
-
case 'id':
|
|
56
|
-
eventId = rawValue;
|
|
57
|
-
break;
|
|
58
|
-
case 'retry': {
|
|
59
|
-
const parsedRetry = Number.parseInt(rawValue, 10);
|
|
60
|
-
retry = Number.isNaN(parsedRetry) ? null : parsedRetry;
|
|
61
|
-
break;
|
|
93
|
+
if (done) {
|
|
94
|
+
if (signal?.aborted) {
|
|
95
|
+
throw normalizeAbortReason(signal.reason, abortMessage);
|
|
62
96
|
}
|
|
63
|
-
|
|
64
|
-
break;
|
|
97
|
+
break;
|
|
65
98
|
}
|
|
66
99
|
}
|
|
67
|
-
if (
|
|
68
|
-
|
|
100
|
+
if (signal?.aborted) {
|
|
101
|
+
throw normalizeAbortReason(signal.reason, abortMessage);
|
|
102
|
+
}
|
|
103
|
+
const finalEvent = flushEvent();
|
|
104
|
+
if (finalEvent) {
|
|
105
|
+
yield finalEvent;
|
|
69
106
|
}
|
|
70
107
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
yield finalEvent;
|
|
108
|
+
finally {
|
|
109
|
+
signal?.removeEventListener('abort', cancelReader);
|
|
74
110
|
}
|
|
75
111
|
}
|
|
76
112
|
// Default to JSON payloads when possible, otherwise preserve the raw text event body.
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
export { createDeepSeek } from './deepseek.js';
|
|
2
|
+
export { createLineAdapter } from './line-adapter.js';
|
|
3
|
+
export { collectProviderMetadata, extractAnthropicMetadata, extractDeepSeekMetadata, extractOllamaMetadata, extractOpenAIMetadata, extractOpenRouterMetadata, mergeProviderMetadata } from './metadata.js';
|
|
4
|
+
export { readLines, parseLineData, isLineError, getLineErrorMessage } from './line-parser.js';
|
|
5
|
+
export { createOllama } from './ollama.js';
|
|
1
6
|
export { createSSEAdapter } from './sse-adapter.js';
|
|
2
7
|
export { readEnv, readErrorBody, mergeHeaders } from './sse-env.js';
|
|
3
8
|
export { readSSE, parseEventData, isErrorEvent, getErrorMessage } from './sse-parser.js';
|
|
4
|
-
export
|
|
9
|
+
export { createOpenRouter } from './openrouter.js';
|
|
10
|
+
export type { AnthropicAdapter, AnthropicChatInput, AnthropicEvent, AnthropicMessage, AnthropicOptions, AnthropicRequest, DeepSeekAdapter, DeepSeekChatInput, DeepSeekEvent, DeepSeekMessage, DeepSeekOptions, DeepSeekRequest, FetchLike, LineAdapter, LineAdapterOptions, LineEvent, LineRequestConfig, OpenAIAdapter, OpenAIChatInput, OpenAIEvent, OpenAIMessage, OpenAIOptions, OpenAIRequest, OllamaAdapter, OllamaChatInput, OllamaEvent, OllamaMessage, OllamaOptions, OllamaRequest, OpenRouterAdapter, OpenRouterChatInput, OpenRouterEvent, OpenRouterMessage, OpenRouterOptions, OpenRouterRequest, ProviderHeaders, ProviderMetadataUpdate, ProviderRetryBackoff, ProviderRetryOptions, ProviderRequestOptions, ProviderStreamMetadata, ProviderUsage, SSEAdapter, SSEAdapterOptions, SSEEvent, SSERequestConfig } from './types.js';
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export { createDeepSeek } from './deepseek.js';
|
|
2
|
+
export { createLineAdapter } from './line-adapter.js';
|
|
3
|
+
export { collectProviderMetadata, extractAnthropicMetadata, extractDeepSeekMetadata, extractOllamaMetadata, extractOpenAIMetadata, extractOpenRouterMetadata, mergeProviderMetadata } from './metadata.js';
|
|
4
|
+
export { readLines, parseLineData, isLineError, getLineErrorMessage } from './line-parser.js';
|
|
5
|
+
export { createOllama } from './ollama.js';
|
|
1
6
|
export { createSSEAdapter } from './sse-adapter.js';
|
|
2
7
|
export { readEnv, readErrorBody, mergeHeaders } from './sse-env.js';
|
|
3
8
|
export { readSSE, parseEventData, isErrorEvent, getErrorMessage } from './sse-parser.js';
|
|
9
|
+
export { createOpenRouter } from './openrouter.js';
|
|
@@ -1,8 +1,32 @@
|
|
|
1
1
|
import type { GlobalAbortSignal, MaybePromise } from '../core/response.js';
|
|
2
2
|
export type ProviderHeaders = Record<string, string>;
|
|
3
|
+
export type ProviderRetryBackoff = number | number[] | 'exponential' | ((attempt: number, error: unknown, retryHint: number | null) => MaybePromise<number>);
|
|
4
|
+
export interface ProviderRetryOptions {
|
|
5
|
+
maxAttempts?: number;
|
|
6
|
+
backoff?: ProviderRetryBackoff;
|
|
7
|
+
resume?: boolean;
|
|
8
|
+
retryOn?: (error: unknown, attempt: number) => MaybePromise<boolean>;
|
|
9
|
+
}
|
|
10
|
+
export interface ProviderUsage {
|
|
11
|
+
inputTokens?: number;
|
|
12
|
+
outputTokens?: number;
|
|
13
|
+
totalTokens?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ProviderStreamMetadata {
|
|
16
|
+
provider: string;
|
|
17
|
+
responseId?: string;
|
|
18
|
+
model?: string;
|
|
19
|
+
finishReason?: string | null;
|
|
20
|
+
stopReason?: string | null;
|
|
21
|
+
usage?: ProviderUsage;
|
|
22
|
+
}
|
|
23
|
+
export interface ProviderMetadataUpdate extends Omit<Partial<ProviderStreamMetadata>, 'usage'> {
|
|
24
|
+
usage?: Partial<ProviderUsage>;
|
|
25
|
+
}
|
|
3
26
|
export interface ProviderRequestOptions {
|
|
4
27
|
signal?: GlobalAbortSignal;
|
|
5
28
|
headers?: ProviderHeaders;
|
|
29
|
+
retry?: ProviderRetryOptions;
|
|
6
30
|
[key: string]: unknown;
|
|
7
31
|
}
|
|
8
32
|
export interface SSEEvent<TData = unknown> {
|
|
@@ -32,12 +56,44 @@ export interface SSEAdapterOptions<TRequest = Record<string, unknown>, TChatInpu
|
|
|
32
56
|
isError?: (event: SSEEvent<TData>) => MaybePromise<boolean>;
|
|
33
57
|
getError?: (event: SSEEvent<TData>, name: string) => MaybePromise<string>;
|
|
34
58
|
eventToText?: (event: SSEEvent<TData>, request: TRequest, requestOptions?: ProviderRequestOptions) => MaybePromise<string | undefined>;
|
|
59
|
+
retry?: ProviderRetryOptions;
|
|
35
60
|
}
|
|
36
61
|
export interface SSEAdapter<TRequest = Record<string, unknown>, TChatInput = unknown, TData = unknown> {
|
|
37
62
|
stream(request?: TRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<SSEEvent<TData>>;
|
|
38
63
|
streamText(request?: TRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
39
64
|
chat(input: TChatInput, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
40
65
|
}
|
|
66
|
+
export interface LineEvent<TData = unknown> {
|
|
67
|
+
line: number;
|
|
68
|
+
raw: string;
|
|
69
|
+
data: TData;
|
|
70
|
+
}
|
|
71
|
+
export interface LineRequestConfig {
|
|
72
|
+
url?: string;
|
|
73
|
+
method?: string;
|
|
74
|
+
headers?: ProviderHeaders;
|
|
75
|
+
signal?: GlobalAbortSignal;
|
|
76
|
+
body?: BodyInit | null;
|
|
77
|
+
[key: string]: unknown;
|
|
78
|
+
}
|
|
79
|
+
export interface LineAdapterOptions<TRequest = Record<string, unknown>, TChatInput = unknown, TData = unknown> {
|
|
80
|
+
name?: string;
|
|
81
|
+
url?: string;
|
|
82
|
+
method?: string;
|
|
83
|
+
headers?: ProviderHeaders;
|
|
84
|
+
fetch?: FetchLike;
|
|
85
|
+
buildRequest?: (request: TRequest, requestOptions?: ProviderRequestOptions) => MaybePromise<TRequest | string | LineRequestConfig>;
|
|
86
|
+
buildChatRequest?: (input: TChatInput, requestOptions?: ProviderRequestOptions) => TRequest;
|
|
87
|
+
parse?: (line: string, event: LineEvent<string>) => MaybePromise<TData>;
|
|
88
|
+
isError?: (event: LineEvent<TData>) => MaybePromise<boolean>;
|
|
89
|
+
getError?: (event: LineEvent<TData>, name: string) => MaybePromise<string>;
|
|
90
|
+
lineToText?: (event: LineEvent<TData>, request: TRequest, requestOptions?: ProviderRequestOptions) => MaybePromise<string | undefined>;
|
|
91
|
+
}
|
|
92
|
+
export interface LineAdapter<TRequest = Record<string, unknown>, TChatInput = unknown, TData = unknown> {
|
|
93
|
+
stream(request?: TRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<LineEvent<TData>>;
|
|
94
|
+
streamText(request?: TRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
95
|
+
chat(input: TChatInput, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
96
|
+
}
|
|
41
97
|
export interface OpenAIEvent {
|
|
42
98
|
type: string;
|
|
43
99
|
[key: string]: unknown;
|
|
@@ -56,6 +112,7 @@ export interface OpenAIOptions {
|
|
|
56
112
|
model?: string;
|
|
57
113
|
headers?: ProviderHeaders;
|
|
58
114
|
fetch?: FetchLike;
|
|
115
|
+
retry?: ProviderRetryOptions;
|
|
59
116
|
}
|
|
60
117
|
export interface OpenAIAdapter {
|
|
61
118
|
responses: {
|
|
@@ -64,6 +121,61 @@ export interface OpenAIAdapter {
|
|
|
64
121
|
streamText(input: string | OpenAIRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
65
122
|
chat(input: OpenAIChatInput, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
66
123
|
}
|
|
124
|
+
export interface OpenRouterEvent {
|
|
125
|
+
id?: string;
|
|
126
|
+
model?: string;
|
|
127
|
+
choices?: Array<{
|
|
128
|
+
index?: number;
|
|
129
|
+
finish_reason?: string | null;
|
|
130
|
+
delta?: {
|
|
131
|
+
role?: string;
|
|
132
|
+
content?: string;
|
|
133
|
+
[key: string]: unknown;
|
|
134
|
+
};
|
|
135
|
+
}>;
|
|
136
|
+
[key: string]: unknown;
|
|
137
|
+
}
|
|
138
|
+
export type OpenRouterMessage = OpenAIMessage;
|
|
139
|
+
export type OpenRouterChatInput = string | OpenRouterMessage | OpenRouterMessage[] | Record<string, unknown>;
|
|
140
|
+
export type OpenRouterRequest = Record<string, unknown> & {
|
|
141
|
+
messages?: OpenRouterChatInput;
|
|
142
|
+
};
|
|
143
|
+
export interface OpenRouterOptions {
|
|
144
|
+
apiKey?: string;
|
|
145
|
+
baseURL?: string;
|
|
146
|
+
model?: string;
|
|
147
|
+
headers?: ProviderHeaders;
|
|
148
|
+
fetch?: FetchLike;
|
|
149
|
+
retry?: ProviderRetryOptions;
|
|
150
|
+
}
|
|
151
|
+
export interface OpenRouterAdapter {
|
|
152
|
+
chatCompletions: {
|
|
153
|
+
stream(request: OpenRouterRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<OpenRouterEvent>;
|
|
154
|
+
};
|
|
155
|
+
streamText(input: string | OpenRouterRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
156
|
+
chat(input: OpenRouterChatInput, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
157
|
+
}
|
|
158
|
+
export type DeepSeekEvent = OpenRouterEvent;
|
|
159
|
+
export type DeepSeekMessage = OpenRouterMessage;
|
|
160
|
+
export type DeepSeekChatInput = string | DeepSeekMessage | DeepSeekMessage[] | Record<string, unknown>;
|
|
161
|
+
export type DeepSeekRequest = Record<string, unknown> & {
|
|
162
|
+
messages?: DeepSeekChatInput;
|
|
163
|
+
};
|
|
164
|
+
export interface DeepSeekOptions {
|
|
165
|
+
apiKey?: string;
|
|
166
|
+
baseURL?: string;
|
|
167
|
+
model?: string;
|
|
168
|
+
headers?: ProviderHeaders;
|
|
169
|
+
fetch?: FetchLike;
|
|
170
|
+
retry?: ProviderRetryOptions;
|
|
171
|
+
}
|
|
172
|
+
export interface DeepSeekAdapter {
|
|
173
|
+
chatCompletions: {
|
|
174
|
+
stream(request: DeepSeekRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<DeepSeekEvent>;
|
|
175
|
+
};
|
|
176
|
+
streamText(input: string | DeepSeekRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
177
|
+
chat(input: DeepSeekChatInput, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
178
|
+
}
|
|
67
179
|
export interface AnthropicEvent {
|
|
68
180
|
type: string;
|
|
69
181
|
[key: string]: unknown;
|
|
@@ -84,6 +196,7 @@ export interface AnthropicOptions {
|
|
|
84
196
|
maxTokens?: number;
|
|
85
197
|
headers?: ProviderHeaders;
|
|
86
198
|
fetch?: FetchLike;
|
|
199
|
+
retry?: ProviderRetryOptions;
|
|
87
200
|
}
|
|
88
201
|
export interface AnthropicAdapter {
|
|
89
202
|
messages: {
|
|
@@ -92,3 +205,35 @@ export interface AnthropicAdapter {
|
|
|
92
205
|
streamText(messages: string | AnthropicRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
93
206
|
chat(input: AnthropicChatInput, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
94
207
|
}
|
|
208
|
+
export interface OllamaEvent {
|
|
209
|
+
model?: string;
|
|
210
|
+
created_at?: string;
|
|
211
|
+
message?: {
|
|
212
|
+
role?: string;
|
|
213
|
+
content?: string;
|
|
214
|
+
[key: string]: unknown;
|
|
215
|
+
};
|
|
216
|
+
done?: boolean;
|
|
217
|
+
done_reason?: string | null;
|
|
218
|
+
error?: string;
|
|
219
|
+
[key: string]: unknown;
|
|
220
|
+
}
|
|
221
|
+
export type OllamaMessage = Record<string, unknown> & {
|
|
222
|
+
role?: string;
|
|
223
|
+
content?: unknown;
|
|
224
|
+
};
|
|
225
|
+
export type OllamaChatInput = string | OllamaMessage | OllamaMessage[] | Record<string, unknown>;
|
|
226
|
+
export type OllamaRequest = Record<string, unknown> & {
|
|
227
|
+
messages?: OllamaChatInput;
|
|
228
|
+
};
|
|
229
|
+
export interface OllamaOptions {
|
|
230
|
+
baseURL?: string;
|
|
231
|
+
model?: string;
|
|
232
|
+
headers?: ProviderHeaders;
|
|
233
|
+
fetch?: FetchLike;
|
|
234
|
+
}
|
|
235
|
+
export interface OllamaAdapter {
|
|
236
|
+
stream(request: OllamaRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<OllamaEvent>;
|
|
237
|
+
streamText(input: string | OllamaRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
238
|
+
chat(input: OllamaChatInput, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
239
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GlobalAbortSignal, MaybePromise, SourceLike } from '../core/response.js';
|
|
2
|
+
export interface SSEFrame<TChunk = unknown> {
|
|
3
|
+
event?: string;
|
|
4
|
+
id?: string;
|
|
5
|
+
retry?: number;
|
|
6
|
+
data: TChunk;
|
|
7
|
+
}
|
|
8
|
+
export interface CreateSSEResponseOptions<TChunk = unknown> {
|
|
9
|
+
signal?: GlobalAbortSignal;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
event?: string;
|
|
12
|
+
doneFrame?: string | false;
|
|
13
|
+
encode?: (chunk: TChunk, index: number) => MaybePromise<SSEFrame<string> | string>;
|
|
14
|
+
onError?: (error: Error) => MaybePromise<SSEFrame<string> | string | false>;
|
|
15
|
+
}
|
|
16
|
+
export declare function createSSEResponse<TChunk>(source: SourceLike<TChunk>, options?: CreateSSEResponseOptions<TChunk>): Response;
|