@qorejs/qore 0.7.1 → 0.7.2
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 +120 -3
- package/dist/src/core/iterable.d.ts +1 -0
- package/dist/src/core/iterable.js +17 -0
- package/dist/src/core/response-runtime.d.ts +2 -0
- package/dist/src/core/response-runtime.js +233 -0
- package/dist/src/core/response-state.d.ts +3 -0
- package/dist/src/core/response-state.js +37 -0
- package/dist/src/core/response-types.d.ts +73 -0
- package/dist/src/core/response-types.js +1 -0
- package/dist/src/core/response.d.ts +4 -0
- package/dist/src/core/response.js +26 -0
- package/dist/src/core/signal-context.d.ts +10 -0
- package/dist/src/core/signal-context.js +69 -0
- package/dist/src/core/signal-nodes.d.ts +45 -0
- package/dist/src/core/signal-nodes.js +197 -0
- package/dist/src/core/signal-scheduler.d.ts +2 -0
- package/dist/src/core/signal-scheduler.js +19 -0
- package/dist/src/core/signal-types.d.ts +19 -0
- package/dist/src/core/signal-types.js +1 -0
- package/dist/src/core/signal.d.ts +19 -0
- package/dist/src/core/signal.js +41 -0
- package/dist/src/core/stream-backpressure.d.ts +3 -0
- package/dist/src/core/stream-backpressure.js +36 -0
- package/dist/src/core/stream-buffer.d.ts +17 -0
- package/dist/src/core/stream-buffer.js +162 -0
- package/dist/src/core/stream-iterator.d.ts +6 -0
- package/dist/src/core/stream-iterator.js +14 -0
- package/dist/src/core/stream-lifecycle.d.ts +10 -0
- package/dist/src/core/stream-lifecycle.js +28 -0
- package/dist/src/core/stream-queue.d.ts +17 -0
- package/dist/src/core/stream-queue.js +62 -0
- package/dist/src/core/stream-runtime.d.ts +2 -0
- package/dist/src/core/stream-runtime.js +127 -0
- package/dist/src/core/stream-source.d.ts +2 -0
- package/dist/src/core/stream-source.js +28 -0
- package/dist/src/core/stream-state.d.ts +4 -0
- package/dist/src/core/stream-state.js +20 -0
- package/dist/src/core/stream-types.d.ts +61 -0
- package/dist/src/core/stream-types.js +1 -0
- package/dist/src/core/stream.d.ts +11 -0
- package/dist/src/core/stream.js +90 -0
- package/dist/src/dom/app.d.ts +38 -0
- package/dist/src/dom/app.js +102 -0
- package/dist/src/dom/dom.d.ts +13 -0
- package/dist/src/dom/dom.js +197 -0
- package/dist/src/dom/properties.d.ts +3 -0
- package/dist/src/dom/properties.js +147 -0
- package/dist/src/dom/reactive.d.ts +6 -0
- package/dist/src/dom/reactive.js +14 -0
- package/dist/src/dom/response-view.d.ts +4 -0
- package/dist/src/dom/response-view.js +37 -0
- package/dist/src/dom/scope.d.ts +10 -0
- package/dist/src/dom/scope.js +49 -0
- package/dist/src/dom/types.d.ts +34 -0
- package/dist/src/dom/types.js +1 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.js +10 -0
- package/dist/src/providers/anthropic.d.ts +2 -0
- package/dist/src/providers/anthropic.js +102 -0
- package/dist/src/providers/openai.d.ts +2 -0
- package/dist/src/providers/openai.js +96 -0
- package/dist/src/providers/sse-adapter.d.ts +2 -0
- package/dist/src/providers/sse-adapter.js +83 -0
- package/dist/src/providers/sse-env.d.ts +4 -0
- package/dist/src/providers/sse-env.js +33 -0
- package/dist/src/providers/sse-parser.d.ts +5 -0
- package/dist/src/providers/sse-parser.js +99 -0
- package/dist/src/providers/sse.d.ts +4 -0
- package/dist/src/providers/sse.js +3 -0
- package/dist/src/providers/types.d.ts +94 -0
- package/dist/src/providers/types.js +1 -0
- package/dist/src/shared/utils.d.ts +2 -0
- package/dist/src/shared/utils.js +32 -0
- package/package.json +25 -11
- package/src/anthropic.js +0 -122
- package/src/app.js +0 -123
- package/src/dom.js +0 -527
- package/src/index.d.ts +0 -405
- package/src/index.js +0 -10
- package/src/iterable.js +0 -20
- package/src/openai.js +0 -112
- package/src/response.js +0 -312
- package/src/signal.js +0 -328
- package/src/sse.js +0 -264
- package/src/stream.js +0 -582
- package/src/utils.js +0 -39
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createSSEAdapter, readEnv } from './sse.js';
|
|
2
|
+
const DEFAULT_BASE_URL = 'https://api.openai.com/v1';
|
|
3
|
+
const DEFAULT_MODEL = 'gpt-5';
|
|
4
|
+
// Treat plain chat text as one user message so callers can start from a single string.
|
|
5
|
+
function normalizeChatInput(input) {
|
|
6
|
+
if (typeof input === 'string') {
|
|
7
|
+
return [{ role: 'user', content: input }];
|
|
8
|
+
}
|
|
9
|
+
if (Array.isArray(input)) {
|
|
10
|
+
return input;
|
|
11
|
+
}
|
|
12
|
+
if (input && typeof input === 'object' && 'role' in input) {
|
|
13
|
+
return [input];
|
|
14
|
+
}
|
|
15
|
+
return input;
|
|
16
|
+
}
|
|
17
|
+
// Keep provider setup explicit because real API keys should stay off the client.
|
|
18
|
+
export function createOpenAI(options = {}) {
|
|
19
|
+
const { apiKey, baseURL = DEFAULT_BASE_URL, model = DEFAULT_MODEL, headers: defaultHeaders = {}, fetch: fetchImpl = globalThis.fetch } = options;
|
|
20
|
+
const resolvedApiKey = apiKey ?? readEnv('OPENAI_API_KEY');
|
|
21
|
+
if (!resolvedApiKey) {
|
|
22
|
+
throw new Error('Qore OpenAI adapter requires an API key. Pass apiKey or set OPENAI_API_KEY.');
|
|
23
|
+
}
|
|
24
|
+
const transport = createSSEAdapter({
|
|
25
|
+
name: 'OpenAI',
|
|
26
|
+
url: `${baseURL}/responses`,
|
|
27
|
+
headers: {
|
|
28
|
+
Authorization: `Bearer ${resolvedApiKey}`,
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
...defaultHeaders
|
|
31
|
+
},
|
|
32
|
+
fetch: fetchImpl,
|
|
33
|
+
buildRequest(request, requestOptions = {}) {
|
|
34
|
+
const { signal, headers = {}, ...overrides } = requestOptions;
|
|
35
|
+
const config = {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers,
|
|
38
|
+
body: JSON.stringify({
|
|
39
|
+
model,
|
|
40
|
+
stream: true,
|
|
41
|
+
...request,
|
|
42
|
+
...overrides
|
|
43
|
+
})
|
|
44
|
+
};
|
|
45
|
+
if (signal) {
|
|
46
|
+
config.signal = signal;
|
|
47
|
+
}
|
|
48
|
+
return config;
|
|
49
|
+
},
|
|
50
|
+
parse: (data) => JSON.parse(data),
|
|
51
|
+
isError: (event) => event.data?.type === 'error',
|
|
52
|
+
getError: (event) => {
|
|
53
|
+
const errorEvent = event.data;
|
|
54
|
+
return errorEvent.error?.message ?? 'OpenAI streaming error';
|
|
55
|
+
},
|
|
56
|
+
eventToText: (event) => event.data?.type === 'response.output_text.delta'
|
|
57
|
+
&& typeof event.data.delta === 'string'
|
|
58
|
+
? event.data.delta
|
|
59
|
+
: undefined
|
|
60
|
+
});
|
|
61
|
+
async function* streamEvents(request, requestOptions = {}) {
|
|
62
|
+
for await (const event of transport.stream(request, requestOptions)) {
|
|
63
|
+
yield event.data;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function* streamText(input, requestOptions = {}) {
|
|
67
|
+
const request = input && typeof input === 'object' && 'input' in input
|
|
68
|
+
? input
|
|
69
|
+
: { input };
|
|
70
|
+
for await (const chunk of transport.streamText(request, requestOptions)) {
|
|
71
|
+
yield chunk;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
// Stream typed semantic events from the Responses API.
|
|
76
|
+
responses: {
|
|
77
|
+
stream: streamEvents
|
|
78
|
+
},
|
|
79
|
+
// Stream only text deltas for the common chat-response case.
|
|
80
|
+
streamText(input, requestOptions = {}) {
|
|
81
|
+
return streamText(input, requestOptions);
|
|
82
|
+
},
|
|
83
|
+
// Match the Qore narrative directly: stream(openai.chat(prompt)).
|
|
84
|
+
chat(input, requestOptions = {}) {
|
|
85
|
+
const { signal, headers, ...rest } = requestOptions;
|
|
86
|
+
const request = { ...rest };
|
|
87
|
+
if (!('input' in request)) {
|
|
88
|
+
request.input = normalizeChatInput(input);
|
|
89
|
+
}
|
|
90
|
+
return streamText(request, {
|
|
91
|
+
...(signal ? { signal } : {}),
|
|
92
|
+
...(headers ? { headers } : {})
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { SSEAdapter, SSEAdapterOptions } from './types.js';
|
|
2
|
+
export declare function createSSEAdapter<TRequest = Record<string, unknown>, TChatInput = unknown, TData = unknown>(options?: SSEAdapterOptions<TRequest, TChatInput, TData>): SSEAdapter<TRequest, TChatInput, TData>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { normalizeError } from '../shared/utils.js';
|
|
2
|
+
import { mergeHeaders, readErrorBody } from './sse-env.js';
|
|
3
|
+
import { getErrorMessage, isErrorEvent, parseEventData, readSSE } from './sse-parser.js';
|
|
4
|
+
function isRequestConfig(value) {
|
|
5
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
// Expose a generic SSE adapter so Qore can integrate with any token-streaming endpoint.
|
|
8
|
+
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;
|
|
10
|
+
if (typeof fetchImpl !== 'function') {
|
|
11
|
+
throw new Error(`Qore ${name} adapter requires fetch in the current runtime`);
|
|
12
|
+
}
|
|
13
|
+
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;
|
|
46
|
+
try {
|
|
47
|
+
parsedEvent = await parse(rawEvent.data, rawEvent);
|
|
48
|
+
}
|
|
49
|
+
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));
|
|
58
|
+
}
|
|
59
|
+
yield nextEvent;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function* streamText(request = {}, requestOptions = {}) {
|
|
63
|
+
for await (const event of stream(request, requestOptions)) {
|
|
64
|
+
const nextText = await eventToText(event, request, requestOptions);
|
|
65
|
+
if (typeof nextText === 'string') {
|
|
66
|
+
yield nextText;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
// Stream parsed SSE events from an arbitrary HTTP endpoint.
|
|
72
|
+
stream,
|
|
73
|
+
// Stream only the text payload selected by the adapter's eventToText mapping.
|
|
74
|
+
streamText,
|
|
75
|
+
// Offer the same narrative shape as provider SDKs when buildChatRequest is supplied.
|
|
76
|
+
chat(input, requestOptions = {}) {
|
|
77
|
+
if (typeof buildChatRequest !== 'function') {
|
|
78
|
+
throw new Error(`Qore ${name} adapter does not define chat(). Use stream() or streamText() instead.`);
|
|
79
|
+
}
|
|
80
|
+
return streamText(buildChatRequest(input, requestOptions), requestOptions);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProviderHeaders } from './types.js';
|
|
2
|
+
export declare function readEnv(name: string): string | undefined;
|
|
3
|
+
export declare function readErrorBody(response: Response): Promise<string>;
|
|
4
|
+
export declare function mergeHeaders(baseHeaders?: ProviderHeaders, nextHeaders?: ProviderHeaders): ProviderHeaders;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Read environment variables without assuming a Node-only runtime.
|
|
2
|
+
export function readEnv(name) {
|
|
3
|
+
return typeof process !== 'undefined' && process?.env
|
|
4
|
+
? process.env[name]
|
|
5
|
+
: undefined;
|
|
6
|
+
}
|
|
7
|
+
// Read one JSON or text error body so adapter failures surface clearly.
|
|
8
|
+
export async function readErrorBody(response) {
|
|
9
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
10
|
+
if (contentType.includes('application/json')) {
|
|
11
|
+
try {
|
|
12
|
+
const body = await response.json();
|
|
13
|
+
return body?.error?.message ?? body?.message ?? JSON.stringify(body);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return `${response.status} ${response.statusText}`.trim();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const text = await response.text();
|
|
21
|
+
return text || `${response.status} ${response.statusText}`.trim();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return `${response.status} ${response.statusText}`.trim();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Merge headers while allowing per-request overrides.
|
|
28
|
+
export function mergeHeaders(baseHeaders = {}, nextHeaders = {}) {
|
|
29
|
+
return {
|
|
30
|
+
...baseHeaders,
|
|
31
|
+
...nextHeaders
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SSEEvent } from './types.js';
|
|
2
|
+
export declare function readSSE(body: ReadableStream<Uint8Array>): AsyncIterable<SSEEvent<string>>;
|
|
3
|
+
export declare function parseEventData(data: string): unknown;
|
|
4
|
+
export declare function isErrorEvent(event: SSEEvent<unknown>): boolean;
|
|
5
|
+
export declare function getErrorMessage(event: SSEEvent<unknown>, name: string): string;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Parse server-sent events without adding any transport dependency.
|
|
2
|
+
export async function* readSSE(body) {
|
|
3
|
+
const reader = body.getReader();
|
|
4
|
+
const decoder = new TextDecoder();
|
|
5
|
+
let buffer = '';
|
|
6
|
+
let eventName = 'message';
|
|
7
|
+
let eventId = null;
|
|
8
|
+
let retry = null;
|
|
9
|
+
let data = [];
|
|
10
|
+
const flushEvent = () => {
|
|
11
|
+
if (data.length === 0) {
|
|
12
|
+
eventName = 'message';
|
|
13
|
+
eventId = null;
|
|
14
|
+
retry = null;
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const nextEvent = {
|
|
18
|
+
event: eventName,
|
|
19
|
+
id: eventId,
|
|
20
|
+
retry,
|
|
21
|
+
data: data.join('\n')
|
|
22
|
+
};
|
|
23
|
+
eventName = 'message';
|
|
24
|
+
eventId = null;
|
|
25
|
+
retry = null;
|
|
26
|
+
data = [];
|
|
27
|
+
return nextEvent;
|
|
28
|
+
};
|
|
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;
|
|
39
|
+
}
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (line.startsWith(':')) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
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;
|
|
62
|
+
}
|
|
63
|
+
default:
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (done) {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const finalEvent = flushEvent();
|
|
72
|
+
if (finalEvent) {
|
|
73
|
+
yield finalEvent;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Default to JSON payloads when possible, otherwise preserve the raw text event body.
|
|
77
|
+
export function parseEventData(data) {
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(data);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Surface provider-side SSE error objects through one shared fallback.
|
|
86
|
+
export function isErrorEvent(event) {
|
|
87
|
+
return Boolean(event.data
|
|
88
|
+
&& typeof event.data === 'object'
|
|
89
|
+
&& 'type' in event.data
|
|
90
|
+
&& event.data.type === 'error');
|
|
91
|
+
}
|
|
92
|
+
// Pull a human-readable message from common SSE error shapes.
|
|
93
|
+
export function getErrorMessage(event, name) {
|
|
94
|
+
if (event.data && typeof event.data === 'object') {
|
|
95
|
+
const errorRecord = event.data;
|
|
96
|
+
return errorRecord.error?.message ?? errorRecord.message ?? `${name} streaming error`;
|
|
97
|
+
}
|
|
98
|
+
return `${name} streaming error`;
|
|
99
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createSSEAdapter } from './sse-adapter.js';
|
|
2
|
+
export { readEnv, readErrorBody, mergeHeaders } from './sse-env.js';
|
|
3
|
+
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';
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { GlobalAbortSignal, MaybePromise } from '../core/response.js';
|
|
2
|
+
export type ProviderHeaders = Record<string, string>;
|
|
3
|
+
export interface ProviderRequestOptions {
|
|
4
|
+
signal?: GlobalAbortSignal;
|
|
5
|
+
headers?: ProviderHeaders;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface SSEEvent<TData = unknown> {
|
|
9
|
+
event: string;
|
|
10
|
+
id: string | null;
|
|
11
|
+
retry: number | null;
|
|
12
|
+
data: TData;
|
|
13
|
+
}
|
|
14
|
+
export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
15
|
+
export interface SSERequestConfig {
|
|
16
|
+
url?: string;
|
|
17
|
+
method?: string;
|
|
18
|
+
headers?: ProviderHeaders;
|
|
19
|
+
signal?: GlobalAbortSignal;
|
|
20
|
+
body?: BodyInit | null;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
export interface SSEAdapterOptions<TRequest = Record<string, unknown>, TChatInput = unknown, TData = unknown> {
|
|
24
|
+
name?: string;
|
|
25
|
+
url?: string;
|
|
26
|
+
method?: string;
|
|
27
|
+
headers?: ProviderHeaders;
|
|
28
|
+
fetch?: FetchLike;
|
|
29
|
+
buildRequest?: (request: TRequest, requestOptions?: ProviderRequestOptions) => MaybePromise<TRequest | string | SSERequestConfig>;
|
|
30
|
+
buildChatRequest?: (input: TChatInput, requestOptions?: ProviderRequestOptions) => TRequest;
|
|
31
|
+
parse?: (data: string, event: SSEEvent<string>) => MaybePromise<TData>;
|
|
32
|
+
isError?: (event: SSEEvent<TData>) => MaybePromise<boolean>;
|
|
33
|
+
getError?: (event: SSEEvent<TData>, name: string) => MaybePromise<string>;
|
|
34
|
+
eventToText?: (event: SSEEvent<TData>, request: TRequest, requestOptions?: ProviderRequestOptions) => MaybePromise<string | undefined>;
|
|
35
|
+
}
|
|
36
|
+
export interface SSEAdapter<TRequest = Record<string, unknown>, TChatInput = unknown, TData = unknown> {
|
|
37
|
+
stream(request?: TRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<SSEEvent<TData>>;
|
|
38
|
+
streamText(request?: TRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
39
|
+
chat(input: TChatInput, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
40
|
+
}
|
|
41
|
+
export interface OpenAIEvent {
|
|
42
|
+
type: string;
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
export type OpenAIMessage = Record<string, unknown> & {
|
|
46
|
+
role?: string;
|
|
47
|
+
content?: unknown;
|
|
48
|
+
};
|
|
49
|
+
export type OpenAIChatInput = string | OpenAIMessage | OpenAIMessage[] | Record<string, unknown>;
|
|
50
|
+
export type OpenAIRequest = Record<string, unknown> & {
|
|
51
|
+
input?: OpenAIChatInput;
|
|
52
|
+
};
|
|
53
|
+
export interface OpenAIOptions {
|
|
54
|
+
apiKey?: string;
|
|
55
|
+
baseURL?: string;
|
|
56
|
+
model?: string;
|
|
57
|
+
headers?: ProviderHeaders;
|
|
58
|
+
fetch?: FetchLike;
|
|
59
|
+
}
|
|
60
|
+
export interface OpenAIAdapter {
|
|
61
|
+
responses: {
|
|
62
|
+
stream(request: OpenAIRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<OpenAIEvent>;
|
|
63
|
+
};
|
|
64
|
+
streamText(input: string | OpenAIRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
65
|
+
chat(input: OpenAIChatInput, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
66
|
+
}
|
|
67
|
+
export interface AnthropicEvent {
|
|
68
|
+
type: string;
|
|
69
|
+
[key: string]: unknown;
|
|
70
|
+
}
|
|
71
|
+
export type AnthropicMessage = Record<string, unknown> & {
|
|
72
|
+
role?: string;
|
|
73
|
+
content?: unknown;
|
|
74
|
+
};
|
|
75
|
+
export type AnthropicChatInput = string | AnthropicMessage | AnthropicMessage[] | Record<string, unknown>;
|
|
76
|
+
export type AnthropicRequest = Record<string, unknown> & {
|
|
77
|
+
messages?: AnthropicChatInput;
|
|
78
|
+
};
|
|
79
|
+
export interface AnthropicOptions {
|
|
80
|
+
apiKey?: string;
|
|
81
|
+
baseURL?: string;
|
|
82
|
+
model?: string;
|
|
83
|
+
version?: string;
|
|
84
|
+
maxTokens?: number;
|
|
85
|
+
headers?: ProviderHeaders;
|
|
86
|
+
fetch?: FetchLike;
|
|
87
|
+
}
|
|
88
|
+
export interface AnthropicAdapter {
|
|
89
|
+
messages: {
|
|
90
|
+
stream(request: AnthropicRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<AnthropicEvent>;
|
|
91
|
+
};
|
|
92
|
+
streamText(messages: string | AnthropicRequest, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
93
|
+
chat(input: AnthropicChatInput, requestOptions?: ProviderRequestOptions): AsyncIterable<string>;
|
|
94
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Convert unknown thrown values into Error instances the runtime can reason about.
|
|
2
|
+
export function normalizeError(error) {
|
|
3
|
+
if (error instanceof Error) {
|
|
4
|
+
return error;
|
|
5
|
+
}
|
|
6
|
+
if (typeof error === 'string') {
|
|
7
|
+
return new Error(error);
|
|
8
|
+
}
|
|
9
|
+
return new Error('Unknown Qore error');
|
|
10
|
+
}
|
|
11
|
+
// Sleep for a fixed time and reject early if the surrounding operation is aborted.
|
|
12
|
+
export function sleep(ms, signal) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const timer = setTimeout(() => {
|
|
15
|
+
cleanup();
|
|
16
|
+
resolve();
|
|
17
|
+
}, ms);
|
|
18
|
+
const onAbort = () => {
|
|
19
|
+
cleanup();
|
|
20
|
+
reject(normalizeError(signal?.reason ?? 'Operation aborted'));
|
|
21
|
+
};
|
|
22
|
+
const cleanup = () => {
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
signal?.removeEventListener('abort', onAbort);
|
|
25
|
+
};
|
|
26
|
+
if (signal?.aborted) {
|
|
27
|
+
onAbort();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
31
|
+
});
|
|
32
|
+
}
|
package/package.json
CHANGED
|
@@ -1,27 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qorejs/qore",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Qore is a streaming-response framework where stream becomes signal.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./src/index.js",
|
|
7
|
-
"types": "./src/index.d.ts",
|
|
6
|
+
"main": "./dist/src/index.js",
|
|
7
|
+
"types": "./dist/src/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./src/index.d.ts",
|
|
11
|
-
"import": "./src/index.js"
|
|
10
|
+
"types": "./dist/src/index.d.ts",
|
|
11
|
+
"import": "./dist/src/index.js"
|
|
12
12
|
},
|
|
13
13
|
"./package.json": "./package.json"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
-
"src",
|
|
16
|
+
"dist/src",
|
|
17
17
|
"README.md",
|
|
18
18
|
"LICENSE"
|
|
19
19
|
],
|
|
20
20
|
"sideEffects": false,
|
|
21
21
|
"scripts": {
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
22
|
+
"build": "node ./scripts/build.mjs",
|
|
23
|
+
"browsers:install": "playwright install chromium",
|
|
24
|
+
"typecheck": "node ./scripts/typecheck.mjs",
|
|
25
|
+
"check:dist": "node ./scripts/check-dist-sync.mjs",
|
|
26
|
+
"test:browser": "node ./scripts/browser-smoke.mjs",
|
|
27
|
+
"smoke:package-types": "node ./scripts/package-type-smoke.mjs",
|
|
28
|
+
"smoke:package-runtime": "node ./scripts/package-runtime-smoke.mjs",
|
|
29
|
+
"test": "node ./scripts/test.mjs",
|
|
30
|
+
"release:check": "node ./scripts/release-check.mjs",
|
|
31
|
+
"publish:github": "npm publish --registry=https://npm.pkg.github.com",
|
|
32
|
+
"publish:npm": "npm publish --access public",
|
|
33
|
+
"prepublishOnly": "node ./scripts/release-check.mjs"
|
|
25
34
|
},
|
|
26
35
|
"engines": {
|
|
27
36
|
"node": ">=18.0.0"
|
|
@@ -39,7 +48,7 @@
|
|
|
39
48
|
"framework",
|
|
40
49
|
"async-iterable"
|
|
41
50
|
],
|
|
42
|
-
"homepage": "https://
|
|
51
|
+
"homepage": "https://qorejs.dev/",
|
|
43
52
|
"repository": {
|
|
44
53
|
"type": "git",
|
|
45
54
|
"url": "git+https://github.com/qorejs/qore.git"
|
|
@@ -50,5 +59,10 @@
|
|
|
50
59
|
"publishConfig": {
|
|
51
60
|
"access": "public"
|
|
52
61
|
},
|
|
53
|
-
"license": "MIT"
|
|
62
|
+
"license": "MIT",
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@playwright/test": "^1.60.0",
|
|
65
|
+
"@types/node": "^25.6.0",
|
|
66
|
+
"typescript": "^6.0.3"
|
|
67
|
+
}
|
|
54
68
|
}
|
package/src/anthropic.js
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { createSSEAdapter, readEnv } from './sse.js';
|
|
2
|
-
|
|
3
|
-
const DEFAULT_BASE_URL = 'https://api.anthropic.com/v1';
|
|
4
|
-
const DEFAULT_MODEL = 'claude-sonnet-4-20250514';
|
|
5
|
-
const DEFAULT_VERSION = '2023-06-01';
|
|
6
|
-
const DEFAULT_MAX_TOKENS = 1024;
|
|
7
|
-
|
|
8
|
-
// Normalize single-string prompts into Anthropic's Messages API shape.
|
|
9
|
-
function normalizeMessages(input) {
|
|
10
|
-
if (typeof input === 'string') {
|
|
11
|
-
return [{ role: 'user', content: input }];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (Array.isArray(input)) {
|
|
15
|
-
return input;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (input && typeof input === 'object' && 'role' in input) {
|
|
19
|
-
return [input];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return input;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Keep provider setup explicit because real API keys should stay off the client.
|
|
26
|
-
export function createAnthropic(options = {}) {
|
|
27
|
-
const {
|
|
28
|
-
apiKey,
|
|
29
|
-
baseURL = DEFAULT_BASE_URL,
|
|
30
|
-
model = DEFAULT_MODEL,
|
|
31
|
-
version = DEFAULT_VERSION,
|
|
32
|
-
maxTokens = DEFAULT_MAX_TOKENS,
|
|
33
|
-
headers: defaultHeaders = {},
|
|
34
|
-
fetch: fetchImpl = globalThis.fetch
|
|
35
|
-
} = options;
|
|
36
|
-
const resolvedApiKey = apiKey ?? readEnv('ANTHROPIC_API_KEY');
|
|
37
|
-
|
|
38
|
-
if (!resolvedApiKey) {
|
|
39
|
-
throw new Error('Qore Anthropic adapter requires an API key. Pass apiKey or set ANTHROPIC_API_KEY.');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const transport = createSSEAdapter({
|
|
43
|
-
name: 'Anthropic',
|
|
44
|
-
url: `${baseURL}/messages`,
|
|
45
|
-
headers: {
|
|
46
|
-
'content-type': 'application/json',
|
|
47
|
-
'anthropic-version': version,
|
|
48
|
-
'x-api-key': resolvedApiKey,
|
|
49
|
-
...defaultHeaders
|
|
50
|
-
},
|
|
51
|
-
fetch: fetchImpl,
|
|
52
|
-
buildRequest(request, requestOptions = {}) {
|
|
53
|
-
const { signal, headers = {}, ...overrides } = requestOptions;
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
method: 'POST',
|
|
57
|
-
signal,
|
|
58
|
-
headers,
|
|
59
|
-
body: JSON.stringify({
|
|
60
|
-
model,
|
|
61
|
-
max_tokens: maxTokens,
|
|
62
|
-
stream: true,
|
|
63
|
-
...request,
|
|
64
|
-
...overrides
|
|
65
|
-
})
|
|
66
|
-
};
|
|
67
|
-
},
|
|
68
|
-
parse: JSON.parse,
|
|
69
|
-
isError: (event) => event.data?.type === 'error',
|
|
70
|
-
getError: (event) => event.data?.error?.message ?? 'Anthropic streaming error',
|
|
71
|
-
eventToText: (event) => (
|
|
72
|
-
event.data?.type === 'content_block_delta'
|
|
73
|
-
&& event.data.delta?.type === 'text_delta'
|
|
74
|
-
&& typeof event.data.delta.text === 'string'
|
|
75
|
-
)
|
|
76
|
-
? event.data.delta.text
|
|
77
|
-
: undefined
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
async function* streamEvents(request, requestOptions = {}) {
|
|
81
|
-
for await (const event of transport.stream(request, requestOptions)) {
|
|
82
|
-
yield event.data;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function* streamText(messages, requestOptions = {}) {
|
|
87
|
-
const request = messages && typeof messages === 'object' && 'messages' in messages
|
|
88
|
-
? messages
|
|
89
|
-
: { messages };
|
|
90
|
-
|
|
91
|
-
for await (const chunk of transport.streamText(request, requestOptions)) {
|
|
92
|
-
yield chunk;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
// Stream typed semantic events from the Messages API.
|
|
98
|
-
messages: {
|
|
99
|
-
stream: streamEvents
|
|
100
|
-
},
|
|
101
|
-
|
|
102
|
-
// Stream only text delta chunks from assistant content blocks.
|
|
103
|
-
streamText(messages, requestOptions = {}) {
|
|
104
|
-
return streamText(messages, requestOptions);
|
|
105
|
-
},
|
|
106
|
-
|
|
107
|
-
// Match the Qore narrative directly: stream(anthropic.chat(prompt)).
|
|
108
|
-
chat(input, requestOptions = {}) {
|
|
109
|
-
const {
|
|
110
|
-
signal,
|
|
111
|
-
headers,
|
|
112
|
-
...request
|
|
113
|
-
} = requestOptions;
|
|
114
|
-
|
|
115
|
-
if (!('messages' in request)) {
|
|
116
|
-
request.messages = normalizeMessages(input);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return streamText(request, { signal, headers });
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
}
|