rightmodeler 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/dist-bundle/cli.js +45845 -0
- package/dist-bundle/proxy/container-supervisor.mjs +172 -0
- package/dist-bundle/proxy/headers.js +10 -0
- package/dist-bundle/proxy/proxy-runtime.mjs +841 -0
- package/dist-bundle/transport/stream.js +309 -0
- package/docs/commands.md +503 -0
- package/docs/evaluators.md +16 -0
- package/docs/exit-codes.md +26 -0
- package/docs/getting-started.md +33 -0
- package/docs/modeb.md +31 -0
- package/package.json +16 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
export const CONTENT_SPOOL_THRESHOLD_BYTES = 1024 * 1024;
|
|
2
|
+
class StreamParseError extends Error {
|
|
3
|
+
}
|
|
4
|
+
class ContentCollector {
|
|
5
|
+
sink;
|
|
6
|
+
signal;
|
|
7
|
+
content = "";
|
|
8
|
+
contentBytes = 0;
|
|
9
|
+
spooling = false;
|
|
10
|
+
pendingWrite = Promise.resolve();
|
|
11
|
+
constructor(sink, signal) {
|
|
12
|
+
this.sink = sink;
|
|
13
|
+
this.signal = signal;
|
|
14
|
+
}
|
|
15
|
+
write(bytes) {
|
|
16
|
+
this.pendingWrite = this.pendingWrite.then(() => this.sink.write(bytes, this.signal));
|
|
17
|
+
return this.pendingWrite;
|
|
18
|
+
}
|
|
19
|
+
async append(value) {
|
|
20
|
+
if (value.length === 0)
|
|
21
|
+
return;
|
|
22
|
+
const bytes = new TextEncoder().encode(value);
|
|
23
|
+
if (!this.spooling &&
|
|
24
|
+
this.contentBytes + bytes.byteLength > CONTENT_SPOOL_THRESHOLD_BYTES) {
|
|
25
|
+
if (this.sink === undefined) {
|
|
26
|
+
throw new Error("Completion exceeded the in-memory limit without a spool sink");
|
|
27
|
+
}
|
|
28
|
+
this.spooling = true;
|
|
29
|
+
await this.write(new TextEncoder().encode(this.content));
|
|
30
|
+
this.content = "";
|
|
31
|
+
}
|
|
32
|
+
if (this.spooling) {
|
|
33
|
+
await this.write(bytes);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
this.content += value;
|
|
37
|
+
this.contentBytes += bytes.byteLength;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async close() {
|
|
41
|
+
if (!this.spooling)
|
|
42
|
+
return null;
|
|
43
|
+
let failure = null;
|
|
44
|
+
await this.pendingWrite.catch(() => {
|
|
45
|
+
failure = "write";
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
await this.sink.close();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
failure = "close";
|
|
52
|
+
}
|
|
53
|
+
return failure;
|
|
54
|
+
}
|
|
55
|
+
result() {
|
|
56
|
+
return this.spooling
|
|
57
|
+
? { content: "", spoolPath: this.sink.path }
|
|
58
|
+
: { content: this.content };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function isRecord(value) {
|
|
62
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
63
|
+
}
|
|
64
|
+
function parseUsage(value) {
|
|
65
|
+
if (!isRecord(value) ||
|
|
66
|
+
typeof value.prompt_tokens !== "number" ||
|
|
67
|
+
typeof value.completion_tokens !== "number" ||
|
|
68
|
+
typeof value.total_tokens !== "number") {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
inputTokens: value.prompt_tokens,
|
|
73
|
+
outputTokens: value.completion_tokens,
|
|
74
|
+
totalTokens: value.total_tokens,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function parseEvent(data) {
|
|
78
|
+
let value;
|
|
79
|
+
try {
|
|
80
|
+
value = JSON.parse(data);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw new StreamParseError(`Stream event was not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
84
|
+
}
|
|
85
|
+
if (!isRecord(value))
|
|
86
|
+
throw new StreamParseError("Stream event must be an object");
|
|
87
|
+
if (value.error !== undefined) {
|
|
88
|
+
return { content: "", finished: false, providerError: true, usage: null };
|
|
89
|
+
}
|
|
90
|
+
if (!Array.isArray(value.choices)) {
|
|
91
|
+
throw new StreamParseError("Stream event choices must be an array");
|
|
92
|
+
}
|
|
93
|
+
let content = "";
|
|
94
|
+
let finished = false;
|
|
95
|
+
for (const rawChoice of value.choices) {
|
|
96
|
+
if (!isRecord(rawChoice))
|
|
97
|
+
throw new StreamParseError("Stream choice must be an object");
|
|
98
|
+
if (rawChoice.finish_reason !== undefined &&
|
|
99
|
+
rawChoice.finish_reason !== null) {
|
|
100
|
+
if (typeof rawChoice.finish_reason !== "string") {
|
|
101
|
+
throw new StreamParseError("finish_reason must be a string or null");
|
|
102
|
+
}
|
|
103
|
+
finished = true;
|
|
104
|
+
}
|
|
105
|
+
if (rawChoice.delta === undefined)
|
|
106
|
+
continue;
|
|
107
|
+
if (!isRecord(rawChoice.delta))
|
|
108
|
+
throw new StreamParseError("Stream choice delta must be an object");
|
|
109
|
+
if (rawChoice.delta.content === undefined ||
|
|
110
|
+
rawChoice.delta.content === null)
|
|
111
|
+
continue;
|
|
112
|
+
if (typeof rawChoice.delta.content !== "string") {
|
|
113
|
+
throw new StreamParseError("Stream delta content must be a string or null");
|
|
114
|
+
}
|
|
115
|
+
content += rawChoice.delta.content;
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
content,
|
|
119
|
+
finished,
|
|
120
|
+
providerError: false,
|
|
121
|
+
usage: value.usage === undefined || value.usage === null
|
|
122
|
+
? null
|
|
123
|
+
: parseUsage(value.usage),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function eventData(event) {
|
|
127
|
+
const data = event
|
|
128
|
+
.split(/\r?\n/)
|
|
129
|
+
.filter((line) => line.startsWith("data:"))
|
|
130
|
+
.map((line) => (line[5] === " " ? line.slice(6) : line.slice(5)));
|
|
131
|
+
return data.length === 0 ? null : data.join("\n");
|
|
132
|
+
}
|
|
133
|
+
async function releaseIterator(iterator) {
|
|
134
|
+
if (iterator.return === undefined)
|
|
135
|
+
return;
|
|
136
|
+
await Promise.resolve()
|
|
137
|
+
.then(() => iterator.return())
|
|
138
|
+
.catch(() => undefined);
|
|
139
|
+
}
|
|
140
|
+
export async function classifyStream(byteStream, options) {
|
|
141
|
+
const iterator = byteStream[Symbol.asyncIterator]();
|
|
142
|
+
let iteratorFinished = false;
|
|
143
|
+
const processingController = new AbortController();
|
|
144
|
+
const collector = new ContentCollector(options.spoolSink, processingController.signal);
|
|
145
|
+
let chunks = 0;
|
|
146
|
+
let usage = null;
|
|
147
|
+
let sawFinish = false;
|
|
148
|
+
let selectedResult;
|
|
149
|
+
const result = (outcome, reason, finishedWithoutSentinel = false) => {
|
|
150
|
+
selectedResult = {
|
|
151
|
+
outcome,
|
|
152
|
+
...(reason === undefined ? {} : { reason }),
|
|
153
|
+
...collector.result(),
|
|
154
|
+
usage,
|
|
155
|
+
chunks,
|
|
156
|
+
...(finishedWithoutSentinel ? { finishedWithoutSentinel: true } : {}),
|
|
157
|
+
};
|
|
158
|
+
return selectedResult;
|
|
159
|
+
};
|
|
160
|
+
const connectionResult = () => sawFinish
|
|
161
|
+
? result("completed", undefined, true)
|
|
162
|
+
: result("truncated", "connection");
|
|
163
|
+
if (options.httpStatus !== undefined &&
|
|
164
|
+
(options.httpStatus < 200 || options.httpStatus >= 300)) {
|
|
165
|
+
await releaseIterator(iterator);
|
|
166
|
+
return result("provider_error", "http");
|
|
167
|
+
}
|
|
168
|
+
if (options.signal?.aborted === true) {
|
|
169
|
+
await releaseIterator(iterator);
|
|
170
|
+
return result("client_cancelled");
|
|
171
|
+
}
|
|
172
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
173
|
+
let buffer = "";
|
|
174
|
+
let idleTimer;
|
|
175
|
+
let hardTimer;
|
|
176
|
+
let stopped = false;
|
|
177
|
+
let stopReason;
|
|
178
|
+
let resolveStop;
|
|
179
|
+
const stop = new Promise((resolve) => {
|
|
180
|
+
resolveStop = resolve;
|
|
181
|
+
});
|
|
182
|
+
const terminate = (reason) => {
|
|
183
|
+
if (stopped)
|
|
184
|
+
return;
|
|
185
|
+
stopped = true;
|
|
186
|
+
stopReason = reason;
|
|
187
|
+
processingController.abort();
|
|
188
|
+
resolveStop();
|
|
189
|
+
};
|
|
190
|
+
const resetIdleTimer = () => {
|
|
191
|
+
clearTimeout(idleTimer);
|
|
192
|
+
idleTimer = setTimeout(() => terminate("idle"), options.idleTimeoutMs);
|
|
193
|
+
};
|
|
194
|
+
const abort = () => terminate("cancelled");
|
|
195
|
+
const stoppedResult = () => {
|
|
196
|
+
if (stopReason === "cancelled")
|
|
197
|
+
return result("client_cancelled");
|
|
198
|
+
return sawFinish
|
|
199
|
+
? result("completed", undefined, true)
|
|
200
|
+
: result("truncated", stopReason);
|
|
201
|
+
};
|
|
202
|
+
const awaitProcessing = async (operation) => {
|
|
203
|
+
const raced = await Promise.race([
|
|
204
|
+
operation.then(() => ({ kind: "done" }), (error) => ({ kind: "error", error })),
|
|
205
|
+
stop.then(() => ({ kind: "stopped" })),
|
|
206
|
+
]);
|
|
207
|
+
if (stopped) {
|
|
208
|
+
operation.catch(() => undefined);
|
|
209
|
+
return { kind: "stopped" };
|
|
210
|
+
}
|
|
211
|
+
return raced;
|
|
212
|
+
};
|
|
213
|
+
hardTimer = setTimeout(() => terminate("deadline"), options.hardDeadlineMs);
|
|
214
|
+
idleTimer = setTimeout(() => terminate("idle"), options.idleTimeoutMs);
|
|
215
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
216
|
+
try {
|
|
217
|
+
for (;;) {
|
|
218
|
+
const next = Promise.resolve()
|
|
219
|
+
.then(() => iterator.next())
|
|
220
|
+
.then((value) => ({ kind: "next", value }), () => ({ kind: "connection_error" }));
|
|
221
|
+
const raced = await Promise.race([
|
|
222
|
+
next,
|
|
223
|
+
stop.then(() => ({ kind: "stopped" })),
|
|
224
|
+
]);
|
|
225
|
+
if (raced.kind === "stopped") {
|
|
226
|
+
return stoppedResult();
|
|
227
|
+
}
|
|
228
|
+
if (raced.kind === "connection_error") {
|
|
229
|
+
return connectionResult();
|
|
230
|
+
}
|
|
231
|
+
if (raced.value.done) {
|
|
232
|
+
iteratorFinished = true;
|
|
233
|
+
try {
|
|
234
|
+
buffer += decoder.decode();
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return connectionResult();
|
|
238
|
+
}
|
|
239
|
+
return connectionResult();
|
|
240
|
+
}
|
|
241
|
+
if (raced.value.value.byteLength === 0)
|
|
242
|
+
continue;
|
|
243
|
+
chunks += 1;
|
|
244
|
+
resetIdleTimer();
|
|
245
|
+
try {
|
|
246
|
+
buffer += decoder.decode(raced.value.value, { stream: true });
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return connectionResult();
|
|
250
|
+
}
|
|
251
|
+
for (;;) {
|
|
252
|
+
const boundary = /\r?\n\r?\n/.exec(buffer);
|
|
253
|
+
if (boundary === null)
|
|
254
|
+
break;
|
|
255
|
+
const rawEvent = buffer.slice(0, boundary.index);
|
|
256
|
+
buffer = buffer.slice(boundary.index + boundary[0].length);
|
|
257
|
+
const data = eventData(rawEvent);
|
|
258
|
+
if (data === null)
|
|
259
|
+
continue;
|
|
260
|
+
if (data === "[DONE]") {
|
|
261
|
+
sawFinish = true;
|
|
262
|
+
return result("completed");
|
|
263
|
+
}
|
|
264
|
+
let event;
|
|
265
|
+
try {
|
|
266
|
+
event = parseEvent(data);
|
|
267
|
+
if (event.providerError)
|
|
268
|
+
return result("provider_error", "provider");
|
|
269
|
+
const processed = await awaitProcessing(collector.append(event.content));
|
|
270
|
+
if (processed.kind === "stopped")
|
|
271
|
+
return stoppedResult();
|
|
272
|
+
if (processed.kind === "error") {
|
|
273
|
+
if (processed.error instanceof Error && !options.spoolSink) {
|
|
274
|
+
throw processed.error;
|
|
275
|
+
}
|
|
276
|
+
return result("truncated", "spool");
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
if (error instanceof StreamParseError) {
|
|
281
|
+
return result("provider_error", "invalid_event");
|
|
282
|
+
}
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
usage = event.usage ?? usage;
|
|
286
|
+
sawFinish ||= event.finished;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
clearTimeout(idleTimer);
|
|
292
|
+
clearTimeout(hardTimer);
|
|
293
|
+
options.signal?.removeEventListener("abort", abort);
|
|
294
|
+
if (!iteratorFinished)
|
|
295
|
+
void releaseIterator(iterator);
|
|
296
|
+
const closeFailure = await collector.close();
|
|
297
|
+
if (closeFailure === "close" ||
|
|
298
|
+
(closeFailure === "write" &&
|
|
299
|
+
selectedResult?.reason !== "idle" &&
|
|
300
|
+
selectedResult?.reason !== "deadline" &&
|
|
301
|
+
selectedResult?.outcome !== "client_cancelled")) {
|
|
302
|
+
if (selectedResult === undefined)
|
|
303
|
+
return result("truncated", "spool");
|
|
304
|
+
selectedResult.outcome = "truncated";
|
|
305
|
+
selectedResult.reason = "spool";
|
|
306
|
+
delete selectedResult.finishedWithoutSentinel;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|