@alia.onl/server 1.0.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/CHANGELOG.md +28 -0
- package/LICENSE +202 -0
- package/NOTICE +15 -0
- package/README.md +135 -0
- package/dist/index.cjs +291 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +236 -0
- package/dist/index.d.ts +236 -0
- package/dist/index.js +283 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var AliaStreamError = class extends Error {
|
|
3
|
+
failure;
|
|
4
|
+
shape;
|
|
5
|
+
constructor(failure, shape = {}) {
|
|
6
|
+
super(`Alia stream could not be read: ${failure}`);
|
|
7
|
+
this.name = "AliaStreamError";
|
|
8
|
+
this.failure = failure;
|
|
9
|
+
this.shape = shape;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var AliaRequestError = class extends Error {
|
|
13
|
+
status;
|
|
14
|
+
code;
|
|
15
|
+
constructor(status, code = null) {
|
|
16
|
+
super(`Alia responded ${status}`);
|
|
17
|
+
this.name = "AliaRequestError";
|
|
18
|
+
this.status = status;
|
|
19
|
+
this.code = code;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var AliaAbortError = class extends Error {
|
|
23
|
+
constructor(message = "The Alia stream was aborted.") {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "AbortError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var MAX_SHAPE_KEYS = 12;
|
|
29
|
+
function isRecord(value) {
|
|
30
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31
|
+
}
|
|
32
|
+
function describeChunk(value, event) {
|
|
33
|
+
const named = {} ;
|
|
34
|
+
if (!isRecord(value)) return { ...named, valueType: value === null ? "null" : typeof value };
|
|
35
|
+
const error = isRecord(value.error) ? value.error : void 0;
|
|
36
|
+
const errorCode = error === void 0 ? void 0 : typeof error.code === "string" ? error.code : typeof error.type === "string" ? error.type : void 0;
|
|
37
|
+
return {
|
|
38
|
+
...named,
|
|
39
|
+
keys: Object.keys(value).slice(0, MAX_SHAPE_KEYS),
|
|
40
|
+
choices: Array.isArray(value.choices) ? value.choices.length : typeof value.choices,
|
|
41
|
+
...errorCode === void 0 ? {} : { errorCode }
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/stream.ts
|
|
46
|
+
var MAX_STREAM_BYTES = 32 * 1024 * 1024;
|
|
47
|
+
var MAX_FRAME_BYTES = 4 * 1024 * 1024;
|
|
48
|
+
var DEFAULT_EVENT_NAME = "message";
|
|
49
|
+
function asOptionalRecord(value) {
|
|
50
|
+
return isRecord(value) ? value : void 0;
|
|
51
|
+
}
|
|
52
|
+
function asStringOrNull(value) {
|
|
53
|
+
return typeof value === "string" ? value : null;
|
|
54
|
+
}
|
|
55
|
+
function* eventsFromChunk(value, shape) {
|
|
56
|
+
if (!isRecord(value)) throw new AliaStreamError("unexpected_chunk", shape());
|
|
57
|
+
if (isRecord(value.error)) {
|
|
58
|
+
const error = value.error;
|
|
59
|
+
yield {
|
|
60
|
+
type: "error",
|
|
61
|
+
code: asStringOrNull(error.code),
|
|
62
|
+
message: typeof error.message === "string" ? error.message : "Alia ended the stream with an error.",
|
|
63
|
+
errorType: asStringOrNull(error.type),
|
|
64
|
+
param: asStringOrNull(error.param)
|
|
65
|
+
};
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!Array.isArray(value.choices)) throw new AliaStreamError("unexpected_chunk", shape());
|
|
69
|
+
const meta = asOptionalRecord(value.alia_meta);
|
|
70
|
+
for (const rawChoice of value.choices) {
|
|
71
|
+
if (!isRecord(rawChoice) || !isRecord(rawChoice.delta)) {
|
|
72
|
+
throw new AliaStreamError("choice_without_delta", shape());
|
|
73
|
+
}
|
|
74
|
+
const delta = rawChoice.delta;
|
|
75
|
+
if (delta.content !== void 0 && delta.content !== null) {
|
|
76
|
+
if (typeof delta.content !== "string") throw new AliaStreamError("content_not_string", shape());
|
|
77
|
+
yield { type: "text", text: delta.content, ...meta === void 0 ? {} : { meta } };
|
|
78
|
+
}
|
|
79
|
+
if (typeof delta.reasoning === "string" && delta.reasoning !== "") {
|
|
80
|
+
yield { type: "reasoning", text: delta.reasoning };
|
|
81
|
+
}
|
|
82
|
+
if (typeof rawChoice.finish_reason === "string") {
|
|
83
|
+
yield { type: "finish", reason: rawChoice.finish_reason, ...meta === void 0 ? {} : { meta } };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function* readAliaEventStream(body, options = {}) {
|
|
88
|
+
const { signal } = options;
|
|
89
|
+
const reader = body.getReader();
|
|
90
|
+
const onAbort = () => {
|
|
91
|
+
void reader.cancel().catch(() => void 0);
|
|
92
|
+
};
|
|
93
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
94
|
+
const decoder = new TextDecoder("utf-8");
|
|
95
|
+
let buffer = "";
|
|
96
|
+
let eventName = "";
|
|
97
|
+
let dataLines = [];
|
|
98
|
+
let totalBytes = 0;
|
|
99
|
+
let sawDone = false;
|
|
100
|
+
const takeFrame = () => {
|
|
101
|
+
const event = eventName;
|
|
102
|
+
const lines = dataLines;
|
|
103
|
+
eventName = "";
|
|
104
|
+
dataLines = [];
|
|
105
|
+
if (lines.length === 0) return null;
|
|
106
|
+
return { event, data: lines.join("\n") };
|
|
107
|
+
};
|
|
108
|
+
function* dispatch(frame) {
|
|
109
|
+
const named = frame.event !== "" && frame.event !== DEFAULT_EVENT_NAME;
|
|
110
|
+
if (!named && frame.data === "[DONE]") {
|
|
111
|
+
sawDone = true;
|
|
112
|
+
yield { type: "done" };
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
let value;
|
|
116
|
+
try {
|
|
117
|
+
value = JSON.parse(frame.data);
|
|
118
|
+
} catch {
|
|
119
|
+
throw new AliaStreamError("malformed_json", { ...named ? { event: frame.event } : {} });
|
|
120
|
+
}
|
|
121
|
+
if (named) {
|
|
122
|
+
yield { type: "event", event: frame.event, data: value };
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
yield* eventsFromChunk(value, () => describeChunk(value));
|
|
126
|
+
}
|
|
127
|
+
const consumeLine = function* (rawLine) {
|
|
128
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
129
|
+
if (line === "") {
|
|
130
|
+
const frame = takeFrame();
|
|
131
|
+
if (frame !== null) yield* dispatch(frame);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (line.startsWith(":")) return;
|
|
135
|
+
const separator = line.indexOf(":");
|
|
136
|
+
const field = separator === -1 ? line : line.slice(0, separator);
|
|
137
|
+
let value = separator === -1 ? "" : line.slice(separator + 1);
|
|
138
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
139
|
+
if (field === "event") eventName = value;
|
|
140
|
+
else if (field === "data") dataLines.push(value);
|
|
141
|
+
};
|
|
142
|
+
try {
|
|
143
|
+
for (; ; ) {
|
|
144
|
+
if (signal?.aborted) throw new AliaAbortError();
|
|
145
|
+
const { done, value } = await reader.read();
|
|
146
|
+
if (signal?.aborted) throw new AliaAbortError();
|
|
147
|
+
if (value !== void 0) {
|
|
148
|
+
totalBytes += value.byteLength;
|
|
149
|
+
if (totalBytes > MAX_STREAM_BYTES) throw new AliaStreamError("oversized");
|
|
150
|
+
}
|
|
151
|
+
buffer += decoder.decode(value, { stream: !done });
|
|
152
|
+
let newline = buffer.indexOf("\n");
|
|
153
|
+
while (newline !== -1) {
|
|
154
|
+
const line = buffer.slice(0, newline);
|
|
155
|
+
buffer = buffer.slice(newline + 1);
|
|
156
|
+
yield* consumeLine(line);
|
|
157
|
+
if (sawDone) return;
|
|
158
|
+
newline = buffer.indexOf("\n");
|
|
159
|
+
}
|
|
160
|
+
if (buffer.length > MAX_FRAME_BYTES) throw new AliaStreamError("oversized");
|
|
161
|
+
if (done) break;
|
|
162
|
+
}
|
|
163
|
+
if (buffer !== "") {
|
|
164
|
+
yield* consumeLine(buffer);
|
|
165
|
+
if (sawDone) return;
|
|
166
|
+
}
|
|
167
|
+
const trailing = takeFrame();
|
|
168
|
+
if (trailing !== null) {
|
|
169
|
+
yield* dispatch(trailing);
|
|
170
|
+
if (sawDone) return;
|
|
171
|
+
}
|
|
172
|
+
throw new AliaStreamError("truncated");
|
|
173
|
+
} finally {
|
|
174
|
+
signal?.removeEventListener("abort", onAbort);
|
|
175
|
+
await reader.cancel().catch(() => void 0);
|
|
176
|
+
reader.releaseLock();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/client.ts
|
|
181
|
+
var ALIA_API_URL = "https://api.alia.onl";
|
|
182
|
+
var CHAT_COMPLETIONS_PATH = "/v1/chat/completions";
|
|
183
|
+
var MAX_ERROR_BODY_BYTES = 4096;
|
|
184
|
+
async function resolve(source) {
|
|
185
|
+
return typeof source === "function" ? await source() : source;
|
|
186
|
+
}
|
|
187
|
+
async function codeFromErrorBody(response) {
|
|
188
|
+
try {
|
|
189
|
+
const reader = response.body?.getReader();
|
|
190
|
+
if (reader === void 0) return null;
|
|
191
|
+
const decoder = new TextDecoder("utf-8");
|
|
192
|
+
let text = "";
|
|
193
|
+
try {
|
|
194
|
+
while (text.length < MAX_ERROR_BODY_BYTES) {
|
|
195
|
+
const { done, value: value2 } = await reader.read();
|
|
196
|
+
if (done) break;
|
|
197
|
+
text += decoder.decode(value2, { stream: true });
|
|
198
|
+
}
|
|
199
|
+
} finally {
|
|
200
|
+
await reader.cancel().catch(() => void 0);
|
|
201
|
+
}
|
|
202
|
+
const value = JSON.parse(text);
|
|
203
|
+
if (!isRecord(value)) return null;
|
|
204
|
+
if (typeof value.code === "string") return value.code;
|
|
205
|
+
if (isRecord(value.error) && typeof value.error.code === "string") return value.error.code;
|
|
206
|
+
return null;
|
|
207
|
+
} catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
var AliaServerClient = class {
|
|
212
|
+
#baseUrl;
|
|
213
|
+
#path;
|
|
214
|
+
#token;
|
|
215
|
+
#headers;
|
|
216
|
+
#fetch;
|
|
217
|
+
constructor(options = {}) {
|
|
218
|
+
this.#baseUrl = (options.baseUrl ?? ALIA_API_URL).replace(/\/+$/, "");
|
|
219
|
+
this.#path = options.path ?? CHAT_COMPLETIONS_PATH;
|
|
220
|
+
this.#token = options.token;
|
|
221
|
+
this.#headers = options.headers;
|
|
222
|
+
this.#fetch = options.fetch ?? fetch;
|
|
223
|
+
}
|
|
224
|
+
/** The absolute URL this client posts to. */
|
|
225
|
+
get url() {
|
|
226
|
+
return `${this.#baseUrl}${this.#path}`;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Start one turn.
|
|
230
|
+
*
|
|
231
|
+
* Resolves once Alia has answered with a 2xx `text/event-stream`; the events
|
|
232
|
+
* arrive as they are written. Throws {@link AliaRequestError} for a non-2xx
|
|
233
|
+
* answer and {@link AliaStreamError} when the response is not a stream this
|
|
234
|
+
* client can read.
|
|
235
|
+
*/
|
|
236
|
+
async stream(request, init = {}) {
|
|
237
|
+
const [token, headers] = await Promise.all([
|
|
238
|
+
init.token === void 0 ? resolve(this.#token) : init.token,
|
|
239
|
+
resolve(this.#headers)
|
|
240
|
+
]);
|
|
241
|
+
const response = await this.#fetch(this.url, {
|
|
242
|
+
method: "POST",
|
|
243
|
+
headers: {
|
|
244
|
+
"Content-Type": "application/json",
|
|
245
|
+
Accept: "text/event-stream",
|
|
246
|
+
...token === void 0 ? {} : { Authorization: `Bearer ${token}` },
|
|
247
|
+
...headers,
|
|
248
|
+
...init.headers
|
|
249
|
+
},
|
|
250
|
+
body: JSON.stringify(buildBody(request)),
|
|
251
|
+
...init.signal === void 0 ? {} : { signal: init.signal }
|
|
252
|
+
});
|
|
253
|
+
if (!response.ok) {
|
|
254
|
+
throw new AliaRequestError(response.status, await codeFromErrorBody(response));
|
|
255
|
+
}
|
|
256
|
+
const mime = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
257
|
+
if (mime !== "text/event-stream") {
|
|
258
|
+
await response.body?.cancel().catch(() => void 0);
|
|
259
|
+
throw new AliaStreamError("not_event_stream", { valueType: mime ?? "none" });
|
|
260
|
+
}
|
|
261
|
+
if (!response.body) throw new AliaStreamError("no_body");
|
|
262
|
+
return readAliaEventStream(response.body, {
|
|
263
|
+
...init.signal === void 0 ? {} : { signal: init.signal }
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
function buildBody(request) {
|
|
268
|
+
const { messages, extra, ...rest } = request;
|
|
269
|
+
const body = { ...extra };
|
|
270
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
271
|
+
if (value !== void 0) body[key] = value;
|
|
272
|
+
}
|
|
273
|
+
body.messages = messages;
|
|
274
|
+
body.stream = true;
|
|
275
|
+
return body;
|
|
276
|
+
}
|
|
277
|
+
function createAliaServerClient(options = {}) {
|
|
278
|
+
return new AliaServerClient(options);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export { ALIA_API_URL, AliaAbortError, AliaRequestError, AliaServerClient, AliaStreamError, createAliaServerClient, readAliaEventStream };
|
|
282
|
+
//# sourceMappingURL=index.js.map
|
|
283
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/stream.ts","../src/client.ts"],"names":["value"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * The SHAPE of a stream chunk this client could not read — never its content.\n *\n * A consumer logs this verbatim. The assistant's text, the person's prompt and\n * any upstream message are deliberately absent: what a reader needs in order to\n * fix a protocol mismatch is which keys arrived, not what they said.\n */\nexport interface AliaChunkShape {\n /** The chunk's top-level keys, capped, when it parsed as a JSON object. */\n readonly keys?: readonly string[];\n /** `choices.length`, or the `typeof` when `choices` was not an array. */\n readonly choices?: number | string;\n /** `error.code` / `error.type` when the chunk carried an error object. */\n readonly errorCode?: string;\n /** The SSE `event:` name the frame carried, when it had one. */\n readonly event?: string;\n /** `typeof` the parsed value, when it was not a JSON object. */\n readonly valueType?: string;\n}\n\n/**\n * Why a stream could not be read. Every one of these is a protocol mismatch\n * between this client and the server, not an answer the server gave.\n */\nexport type AliaStreamFailure =\n /** The response was 2xx but not `text/event-stream`. */\n | 'not_event_stream'\n /** The response was 2xx, SSE, and had no readable body. */\n | 'no_body'\n /** A `data:` payload was not JSON. */\n | 'malformed_json'\n /** A chunk parsed, but is neither an OpenAI chunk nor an error payload. */\n | 'unexpected_chunk'\n /** A choice arrived without a `delta` object. */\n | 'choice_without_delta'\n /** `delta.content` was present and not a string. */\n | 'content_not_string'\n /** The body ended before `data: [DONE]`. */\n | 'truncated'\n /** The stream exceeded this client's byte or frame limits. */\n | 'oversized';\n\n/**\n * The stream itself could not be read.\n *\n * Distinct from {@link AliaRequestError}, which means the server answered and\n * said no. This one means the bytes did not match the protocol, and it always\n * carries the shape that broke it so the mismatch is nameable from a log line.\n */\nexport class AliaStreamError extends Error {\n readonly failure: AliaStreamFailure;\n readonly shape: AliaChunkShape;\n\n constructor(failure: AliaStreamFailure, shape: AliaChunkShape = {}) {\n super(`Alia stream could not be read: ${failure}`);\n this.name = 'AliaStreamError';\n this.failure = failure;\n this.shape = shape;\n }\n}\n\n/**\n * Alia answered with a non-2xx status.\n *\n * `code` is the machine-readable code from the body when the body was small\n * JSON that carried one — `code` or `error.code`. The body's prose is NOT\n * retained: an upstream message is not a consumer's to show, and a client that\n * keeps it invites a product surface to print it.\n */\nexport class AliaRequestError extends Error {\n readonly status: number;\n readonly code: string | null;\n\n constructor(status: number, code: string | null = null) {\n super(`Alia responded ${status}`);\n this.name = 'AliaRequestError';\n this.status = status;\n this.code = code;\n }\n}\n\n/** The caller's `AbortSignal` fired. `name` is `AbortError`, as fetch's is. */\nexport class AliaAbortError extends Error {\n constructor(message = 'The Alia stream was aborted.') {\n super(message);\n this.name = 'AbortError';\n }\n}\n\nconst MAX_SHAPE_KEYS = 12;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Describe a parsed chunk without repeating anything it said. */\nexport function describeChunk(value: unknown, event?: string): AliaChunkShape {\n const named = event === undefined || event === '' ? {} : { event };\n if (!isRecord(value)) return { ...named, valueType: value === null ? 'null' : typeof value };\n\n const error = isRecord(value.error) ? value.error : undefined;\n const errorCode =\n error === undefined\n ? undefined\n : typeof error.code === 'string'\n ? error.code\n : typeof error.type === 'string'\n ? error.type\n : undefined;\n\n return {\n ...named,\n keys: Object.keys(value).slice(0, MAX_SHAPE_KEYS),\n choices: Array.isArray(value.choices) ? value.choices.length : typeof value.choices,\n ...(errorCode === undefined ? {} : { errorCode }),\n };\n}\n\nexport { isRecord };\n","import {\n AliaAbortError,\n AliaStreamError,\n describeChunk,\n isRecord,\n type AliaChunkShape,\n} from './errors.js';\nimport type { AliaStreamEvent } from './events.js';\n\n/** Ceilings, not tuning knobs: a runaway stream must end as an error, not as RAM. */\nconst MAX_STREAM_BYTES = 32 * 1024 * 1024;\nconst MAX_FRAME_BYTES = 4 * 1024 * 1024;\n\n/** SSE's default event type. A frame naming it is an ordinary data frame. */\nconst DEFAULT_EVENT_NAME = 'message';\n\nexport interface ReadAliaEventStreamOptions {\n readonly signal?: AbortSignal;\n}\n\nfunction asOptionalRecord(value: unknown): Readonly<Record<string, unknown>> | undefined {\n return isRecord(value) ? value : undefined;\n}\n\nfunction asStringOrNull(value: unknown): string | null {\n return typeof value === 'string' ? value : null;\n}\n\n/** Turn one parsed `data:` payload into zero or more events. */\nfunction* eventsFromChunk(value: unknown, shape: () => AliaChunkShape): Generator<AliaStreamEvent> {\n if (!isRecord(value)) throw new AliaStreamError('unexpected_chunk', shape());\n\n // An in-stream error payload. Alia writes `[DONE]` straight after it, so this\n // yields rather than returns: the terminal event still arrives, and a consumer\n // that stops on the error simply never asks for it.\n if (isRecord(value.error)) {\n const error = value.error;\n yield {\n type: 'error',\n code: asStringOrNull(error.code),\n message: typeof error.message === 'string' ? error.message : 'Alia ended the stream with an error.',\n errorType: asStringOrNull(error.type),\n param: asStringOrNull(error.param),\n };\n return;\n }\n\n if (!Array.isArray(value.choices)) throw new AliaStreamError('unexpected_chunk', shape());\n\n // A usage-only frame (`stream_options.include_usage`) carries no choices.\n const meta = asOptionalRecord(value.alia_meta);\n\n for (const rawChoice of value.choices) {\n if (!isRecord(rawChoice) || !isRecord(rawChoice.delta)) {\n throw new AliaStreamError('choice_without_delta', shape());\n }\n const delta = rawChoice.delta;\n\n if (delta.content !== undefined && delta.content !== null) {\n if (typeof delta.content !== 'string') throw new AliaStreamError('content_not_string', shape());\n yield { type: 'text', text: delta.content, ...(meta === undefined ? {} : { meta }) };\n }\n if (typeof delta.reasoning === 'string' && delta.reasoning !== '') {\n yield { type: 'reasoning', text: delta.reasoning };\n }\n if (typeof rawChoice.finish_reason === 'string') {\n yield { type: 'finish', reason: rawChoice.finish_reason, ...(meta === undefined ? {} : { meta }) };\n }\n }\n}\n\ninterface Frame {\n readonly event: string;\n readonly data: string;\n}\n\n/**\n * Read Alia's SSE body as typed events.\n *\n * Exported for a consumer that already holds a `Response` — the client below is\n * this function plus the request and its status handling. The generator owns the\n * reader: leaving the loop early (a `break`, a thrown consumer) cancels it.\n */\nexport async function* readAliaEventStream(\n body: ReadableStream<Uint8Array>,\n options: ReadAliaEventStreamOptions = {},\n): AsyncGenerator<AliaStreamEvent, void, undefined> {\n const { signal } = options;\n const reader = body.getReader();\n const onAbort = (): void => {\n void reader.cancel().catch(() => undefined);\n };\n signal?.addEventListener('abort', onAbort, { once: true });\n\n const decoder = new TextDecoder('utf-8');\n let buffer = '';\n let eventName = '';\n let dataLines: string[] = [];\n let totalBytes = 0;\n let sawDone = false;\n\n const takeFrame = (): Frame | null => {\n const event = eventName;\n const lines = dataLines;\n eventName = '';\n dataLines = [];\n if (lines.length === 0) return null;\n return { event, data: lines.join('\\n') };\n };\n\n /** One complete frame → events. Returns false once `[DONE]` has been seen. */\n function* dispatch(frame: Frame): Generator<AliaStreamEvent> {\n const named = frame.event !== '' && frame.event !== DEFAULT_EVENT_NAME;\n\n if (!named && frame.data === '[DONE]') {\n sawDone = true;\n yield { type: 'done' };\n return;\n }\n\n let value: unknown;\n try {\n value = JSON.parse(frame.data) as unknown;\n } catch {\n throw new AliaStreamError('malformed_json', { ...(named ? { event: frame.event } : {}) });\n }\n\n if (named) {\n yield { type: 'event', event: frame.event, data: value };\n return;\n }\n yield* eventsFromChunk(value, () => describeChunk(value));\n }\n\n const consumeLine = function* (rawLine: string): Generator<AliaStreamEvent> {\n const line = rawLine.endsWith('\\r') ? rawLine.slice(0, -1) : rawLine;\n if (line === '') {\n const frame = takeFrame();\n if (frame !== null) yield* dispatch(frame);\n return;\n }\n if (line.startsWith(':')) return;\n\n const separator = line.indexOf(':');\n const field = separator === -1 ? line : line.slice(0, separator);\n let value = separator === -1 ? '' : line.slice(separator + 1);\n if (value.startsWith(' ')) value = value.slice(1);\n\n if (field === 'event') eventName = value;\n else if (field === 'data') dataLines.push(value);\n // `id` and `retry` are SSE's own and carry nothing Alia uses; anything else\n // is an unknown field, which the SSE spec requires be ignored.\n };\n\n try {\n for (;;) {\n if (signal?.aborted) throw new AliaAbortError();\n const { done, value } = await reader.read();\n if (signal?.aborted) throw new AliaAbortError();\n\n if (value !== undefined) {\n totalBytes += value.byteLength;\n if (totalBytes > MAX_STREAM_BYTES) throw new AliaStreamError('oversized');\n }\n buffer += decoder.decode(value, { stream: !done });\n\n let newline = buffer.indexOf('\\n');\n while (newline !== -1) {\n const line = buffer.slice(0, newline);\n buffer = buffer.slice(newline + 1);\n yield* consumeLine(line);\n if (sawDone) return;\n newline = buffer.indexOf('\\n');\n }\n if (buffer.length > MAX_FRAME_BYTES) throw new AliaStreamError('oversized');\n\n if (done) break;\n }\n\n // A body that ends without its blank line still holds a complete frame.\n if (buffer !== '') {\n yield* consumeLine(buffer);\n if (sawDone) return;\n }\n const trailing = takeFrame();\n if (trailing !== null) {\n yield* dispatch(trailing);\n if (sawDone) return;\n }\n\n // Reaching here means the body ended with no `[DONE]`. The answer is\n // incomplete and saying so is the whole point: a truncated turn that reads\n // as a successful short one is indistinguishable from a real short one.\n throw new AliaStreamError('truncated');\n } finally {\n signal?.removeEventListener('abort', onAbort);\n await reader.cancel().catch(() => undefined);\n reader.releaseLock();\n }\n}\n","import { AliaRequestError, AliaStreamError, isRecord } from './errors.js';\nimport type { AliaStreamEvent } from './events.js';\nimport { readAliaEventStream } from './stream.js';\n\n/** Alia's public product API. */\nexport const ALIA_API_URL = 'https://api.alia.onl';\n\n/** Alia's permanent, OpenAI-compatible chat path (ADR 0010). */\nconst CHAT_COMPLETIONS_PATH = '/v1/chat/completions';\n\n/** How much of a non-2xx body is read to look for a machine-readable code. */\nconst MAX_ERROR_BODY_BYTES = 4096;\n\nexport type AliaHeaders = Readonly<Record<string, string>>;\n\nexport interface AliaChatMessage {\n readonly role: 'system' | 'user' | 'assistant' | 'tool';\n readonly content: string;\n}\n\n/**\n * One turn. `stream` is not an option: this client only streams, which is why\n * it can promise that every byte lands in a typed event.\n */\nexport interface AliaChatCompletionRequest {\n readonly messages: readonly AliaChatMessage[];\n /** The Alia agent this turn runs as. */\n readonly agentId?: string;\n /** A routing-profile id. Omit it and Alia picks the product default. */\n readonly model?: string;\n readonly conversationId?: string;\n readonly reasoningEffort?: string;\n readonly webSearch?: boolean;\n readonly deepResearch?: boolean;\n readonly clientContext?: string;\n /**\n * Anything else the product route accepts. Merged UNDER the fields above, so\n * a typed field always wins and `stream` can never be turned off.\n */\n readonly extra?: Readonly<Record<string, unknown>>;\n}\n\nexport interface AliaServerClientOptions {\n /** e.g. `https://api.alia.onl`. A trailing slash is ignored. */\n readonly baseUrl?: string;\n /**\n * The calling product's Oxy service token — the bearer Oxy bills. A function\n * is called once per turn, for a credential that rotates.\n */\n readonly token?: string | (() => string | Promise<string>);\n /**\n * Extra headers. A function is called once per turn, which is what a one-use\n * header such as `X-Oxy-Requester-Assertion` needs: Oxy consumes each\n * assertion, so it must be minted per request and never cached.\n */\n readonly headers?: AliaHeaders | (() => AliaHeaders | Promise<AliaHeaders>);\n /** Injected for tests. Defaults to the global `fetch`. */\n readonly fetch?: typeof fetch;\n /** Override the chat path. Defaults to `/v1/chat/completions`. */\n readonly path?: string;\n}\n\nexport interface AliaStreamInit {\n readonly signal?: AbortSignal;\n /** This turn's bearer, overriding the client's. */\n readonly token?: string;\n /** This turn's extra headers, merged over the client's. */\n readonly headers?: AliaHeaders;\n}\n\nasync function resolve<T>(source: T | (() => T | Promise<T>) | undefined): Promise<T | undefined> {\n return typeof source === 'function' ? await (source as () => T | Promise<T>)() : source;\n}\n\n/** The machine-readable code from an error body, and nothing else from it. */\nasync function codeFromErrorBody(response: Response): Promise<string | null> {\n try {\n const reader = response.body?.getReader();\n if (reader === undefined) return null;\n const decoder = new TextDecoder('utf-8');\n let text = '';\n try {\n while (text.length < MAX_ERROR_BODY_BYTES) {\n const { done, value } = await reader.read();\n if (done) break;\n text += decoder.decode(value, { stream: true });\n }\n } finally {\n await reader.cancel().catch(() => undefined);\n }\n const value: unknown = JSON.parse(text);\n if (!isRecord(value)) return null;\n if (typeof value.code === 'string') return value.code;\n if (isRecord(value.error) && typeof value.error.code === 'string') return value.error.code;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * A backend's client for Alia's product API.\n *\n * It exists because every consumer that called Alia from a server wrote the same\n * SSE parser, and each one got a different subset of the wire right. Alia writes\n * OpenAI-shaped chunks, named product events, synthetic recovery chunks carrying\n * `alia_meta`, in-stream error payloads and a terminal `[DONE]` — five shapes,\n * of which a hand-rolled reader typically handles two and silently drops the\n * one that matters when something is wrong.\n *\n * Node 22+. No React, no DOM beyond `fetch` and `ReadableStream`.\n */\nexport class AliaServerClient {\n readonly #baseUrl: string;\n readonly #path: string;\n readonly #token: AliaServerClientOptions['token'];\n readonly #headers: AliaServerClientOptions['headers'];\n readonly #fetch: typeof fetch;\n\n constructor(options: AliaServerClientOptions = {}) {\n this.#baseUrl = (options.baseUrl ?? ALIA_API_URL).replace(/\\/+$/, '');\n this.#path = options.path ?? CHAT_COMPLETIONS_PATH;\n this.#token = options.token;\n this.#headers = options.headers;\n this.#fetch = options.fetch ?? fetch;\n }\n\n /** The absolute URL this client posts to. */\n get url(): string {\n return `${this.#baseUrl}${this.#path}`;\n }\n\n /**\n * Start one turn.\n *\n * Resolves once Alia has answered with a 2xx `text/event-stream`; the events\n * arrive as they are written. Throws {@link AliaRequestError} for a non-2xx\n * answer and {@link AliaStreamError} when the response is not a stream this\n * client can read.\n */\n async stream(\n request: AliaChatCompletionRequest,\n init: AliaStreamInit = {},\n ): Promise<AsyncIterable<AliaStreamEvent>> {\n const [token, headers] = await Promise.all([\n init.token === undefined ? resolve(this.#token) : init.token,\n resolve(this.#headers),\n ]);\n\n const response = await this.#fetch(this.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n ...(token === undefined ? {} : { Authorization: `Bearer ${token}` }),\n ...headers,\n ...init.headers,\n },\n body: JSON.stringify(buildBody(request)),\n ...(init.signal === undefined ? {} : { signal: init.signal }),\n });\n\n if (!response.ok) {\n throw new AliaRequestError(response.status, await codeFromErrorBody(response));\n }\n\n const mime = response.headers.get('content-type')?.split(';', 1)[0]?.trim().toLowerCase();\n if (mime !== 'text/event-stream') {\n await response.body?.cancel().catch(() => undefined);\n throw new AliaStreamError('not_event_stream', { valueType: mime ?? 'none' });\n }\n if (!response.body) throw new AliaStreamError('no_body');\n\n return readAliaEventStream(response.body, {\n ...(init.signal === undefined ? {} : { signal: init.signal }),\n });\n }\n}\n\n/** The wire body: declared fields only, undefined dropped, `stream` pinned on. */\nfunction buildBody(request: AliaChatCompletionRequest): Record<string, unknown> {\n const { messages, extra, ...rest } = request;\n const body: Record<string, unknown> = { ...extra };\n for (const [key, value] of Object.entries(rest)) {\n if (value !== undefined) body[key] = value;\n }\n body.messages = messages;\n body.stream = true;\n return body;\n}\n\n/** Convenience factory, for callers that would rather not write `new`. */\nexport function createAliaServerClient(options: AliaServerClientOptions = {}): AliaServerClient {\n return new AliaServerClient(options);\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alia.onl/server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Alia's server-side streaming client — one typed event stream for a backend that calls Alia's product API",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"private": false,
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"NOTICE",
|
|
22
|
+
"CHANGELOG.md"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"prepublishOnly": "bun run build",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"test": "vitest run"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"alia",
|
|
39
|
+
"oxy",
|
|
40
|
+
"sse",
|
|
41
|
+
"streaming",
|
|
42
|
+
"chat"
|
|
43
|
+
],
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/express": "^5.0.6",
|
|
46
|
+
"@types/node": "^25.9.5",
|
|
47
|
+
"tsup": "^8.5.1",
|
|
48
|
+
"typescript": "^5.9.3",
|
|
49
|
+
"vitest": "^4.1.10"
|
|
50
|
+
}
|
|
51
|
+
}
|