@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.
Files changed (47) hide show
  1. package/README.md +294 -5
  2. package/dist/src/core/owner.d.ts +14 -0
  3. package/dist/src/core/owner.js +66 -0
  4. package/dist/src/core/response-runtime.js +5 -5
  5. package/dist/src/core/response-state.d.ts +4 -0
  6. package/dist/src/core/response-state.js +67 -2
  7. package/dist/src/core/signal-context.js +19 -9
  8. package/dist/src/core/signal-nodes.d.ts +5 -0
  9. package/dist/src/core/signal-nodes.js +51 -21
  10. package/dist/src/core/signal-types.d.ts +2 -0
  11. package/dist/src/core/signal.d.ts +1 -0
  12. package/dist/src/core/signal.js +2 -0
  13. package/dist/src/core/stream-types.d.ts +12 -0
  14. package/dist/src/core/stream.d.ts +1 -1
  15. package/dist/src/core/stream.js +144 -0
  16. package/dist/src/dom/app.js +2 -3
  17. package/dist/src/dom/dom.d.ts +1 -0
  18. package/dist/src/dom/dom.js +96 -9
  19. package/dist/src/dom/scope.d.ts +3 -1
  20. package/dist/src/dom/scope.js +10 -3
  21. package/dist/src/index.d.ts +10 -4
  22. package/dist/src/index.js +7 -2
  23. package/dist/src/providers/anthropic.js +6 -4
  24. package/dist/src/providers/deepseek.d.ts +2 -0
  25. package/dist/src/providers/deepseek.js +90 -0
  26. package/dist/src/providers/line-adapter.d.ts +2 -0
  27. package/dist/src/providers/line-adapter.js +83 -0
  28. package/dist/src/providers/line-parser.d.ts +5 -0
  29. package/dist/src/providers/line-parser.js +103 -0
  30. package/dist/src/providers/metadata.d.ts +8 -0
  31. package/dist/src/providers/metadata.js +193 -0
  32. package/dist/src/providers/ollama.d.ts +2 -0
  33. package/dist/src/providers/ollama.js +82 -0
  34. package/dist/src/providers/openai.js +6 -4
  35. package/dist/src/providers/openrouter.d.ts +2 -0
  36. package/dist/src/providers/openrouter.js +90 -0
  37. package/dist/src/providers/sse-adapter.js +129 -44
  38. package/dist/src/providers/sse-parser.d.ts +1 -1
  39. package/dist/src/providers/sse-parser.js +74 -38
  40. package/dist/src/providers/sse.d.ts +7 -1
  41. package/dist/src/providers/sse.js +6 -0
  42. package/dist/src/providers/types.d.ts +145 -0
  43. package/dist/src/server/sse-response.d.ts +16 -0
  44. package/dist/src/server/sse-response.js +102 -0
  45. package/dist/src/shared/utils.d.ts +1 -0
  46. package/dist/src/shared/utils.js +8 -1
  47. package/package.json +9 -2
@@ -0,0 +1,2 @@
1
+ import type { OpenRouterAdapter, OpenRouterOptions } from './types.js';
2
+ export declare function createOpenRouter(options?: OpenRouterOptions): OpenRouterAdapter;
@@ -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 builtRequest = buildRequest
15
- ? await buildRequest(request, requestOptions)
16
- : request;
17
- const requestConfig = isRequestConfig(builtRequest)
18
- ? builtRequest
19
- : { body: builtRequest };
20
- const { url = defaultURL, method = defaultMethod, headers: requestHeaders = {}, signal: requestSignal, ...init } = requestConfig;
21
- const { signal: overrideSignal, headers: overrideHeaders = {} } = requestOptions;
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
- parsedEvent = await parse(rawEvent.data, rawEvent);
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
- throw normalizeError(error);
51
- }
52
- const nextEvent = {
53
- ...rawEvent,
54
- data: parsedEvent
55
- };
56
- if (await isError(nextEvent)) {
57
- throw new Error(await getError(nextEvent, name));
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>): AsyncIterable<SSEEvent<string>>;
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
- while (true) {
30
- const { value, done } = await reader.read();
31
- buffer += decoder.decode(value ?? new Uint8Array(), { stream: !done });
32
- const lines = buffer.split(/\r?\n/);
33
- buffer = lines.pop() ?? '';
34
- for (const line of lines) {
35
- if (!line) {
36
- const nextEvent = flushEvent();
37
- if (nextEvent) {
38
- yield nextEvent;
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
- continue;
54
+ throw error;
41
55
  }
42
- if (line.startsWith(':')) {
43
- continue;
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
- const separator = line.indexOf(':');
46
- const field = separator === -1 ? line : line.slice(0, separator);
47
- const rawValue = separator === -1 ? '' : line.slice(separator + 1).replace(/^ /, '');
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
- default:
64
- break;
97
+ break;
65
98
  }
66
99
  }
67
- if (done) {
68
- break;
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
- const finalEvent = flushEvent();
72
- if (finalEvent) {
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 type { AnthropicAdapter, AnthropicChatInput, AnthropicEvent, AnthropicMessage, AnthropicOptions, AnthropicRequest, FetchLike, OpenAIAdapter, OpenAIChatInput, OpenAIEvent, OpenAIMessage, OpenAIOptions, OpenAIRequest, ProviderHeaders, ProviderRequestOptions, SSEAdapter, SSEAdapterOptions, SSEEvent, SSERequestConfig } from './types.js';
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;