@sheepbun/yips 0.1.1 → 0.1.46
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 +52 -0
- package/bin/yips.js +15 -0
- package/package.json +21 -128
- package/postinstall.js +50 -0
- package/dist/agent/commands/command-catalog.js +0 -243
- package/dist/agent/commands/commands.js +0 -418
- package/dist/agent/conductor.js +0 -118
- package/dist/agent/context/code-context.js +0 -68
- package/dist/agent/context/memory-store.js +0 -159
- package/dist/agent/context/session-store.js +0 -211
- package/dist/agent/protocol/tool-protocol.js +0 -160
- package/dist/agent/skills/skills.js +0 -327
- package/dist/agent/tools/tool-executor.js +0 -415
- package/dist/agent/tools/tool-safety.js +0 -52
- package/dist/app/index.js +0 -35
- package/dist/app/repl.js +0 -105
- package/dist/app/update-check.js +0 -132
- package/dist/app/version.js +0 -51
- package/dist/code-context.js +0 -68
- package/dist/colors.js +0 -204
- package/dist/command-catalog.js +0 -242
- package/dist/commands.js +0 -350
- package/dist/conductor.js +0 -94
- package/dist/config/config.js +0 -335
- package/dist/config/hooks.js +0 -187
- package/dist/config.js +0 -335
- package/dist/downloader-state.js +0 -302
- package/dist/downloader-ui.js +0 -289
- package/dist/gateway/adapters/discord.js +0 -108
- package/dist/gateway/adapters/formatting.js +0 -96
- package/dist/gateway/adapters/telegram.js +0 -106
- package/dist/gateway/adapters/types.js +0 -2
- package/dist/gateway/adapters/whatsapp.js +0 -124
- package/dist/gateway/auth-policy.js +0 -66
- package/dist/gateway/core.js +0 -87
- package/dist/gateway/headless-conductor.js +0 -328
- package/dist/gateway/message-router.js +0 -23
- package/dist/gateway/rate-limiter.js +0 -48
- package/dist/gateway/runtime/backend-policy.js +0 -18
- package/dist/gateway/runtime/discord-bot.js +0 -104
- package/dist/gateway/runtime/discord-main.js +0 -69
- package/dist/gateway/session-manager.js +0 -77
- package/dist/gateway/types.js +0 -2
- package/dist/hardware.js +0 -92
- package/dist/hooks.js +0 -187
- package/dist/index.js +0 -34
- package/dist/input-engine.js +0 -250
- package/dist/llama-client.js +0 -227
- package/dist/llama-server.js +0 -620
- package/dist/llm/llama-client.js +0 -227
- package/dist/llm/llama-server.js +0 -620
- package/dist/llm/token-counter.js +0 -47
- package/dist/memory-store.js +0 -159
- package/dist/messages.js +0 -59
- package/dist/model-downloader.js +0 -382
- package/dist/model-manager-state.js +0 -118
- package/dist/model-manager-ui.js +0 -194
- package/dist/model-manager.js +0 -190
- package/dist/models/hardware.js +0 -92
- package/dist/models/model-downloader.js +0 -382
- package/dist/models/model-manager.js +0 -190
- package/dist/prompt-box.js +0 -78
- package/dist/prompt-composer.js +0 -498
- package/dist/repl.js +0 -105
- package/dist/session-store.js +0 -211
- package/dist/spinner.js +0 -76
- package/dist/title-box.js +0 -388
- package/dist/token-counter.js +0 -47
- package/dist/tool-executor.js +0 -415
- package/dist/tool-protocol.js +0 -121
- package/dist/tool-safety.js +0 -52
- package/dist/tui/app.js +0 -2553
- package/dist/tui/startup.js +0 -56
- package/dist/tui-input-routing.js +0 -53
- package/dist/tui.js +0 -51
- package/dist/types/app-types.js +0 -2
- package/dist/types.js +0 -2
- package/dist/ui/colors.js +0 -204
- package/dist/ui/downloader/downloader-state.js +0 -302
- package/dist/ui/downloader/downloader-ui.js +0 -289
- package/dist/ui/input/input-engine.js +0 -250
- package/dist/ui/input/tui-input-routing.js +0 -53
- package/dist/ui/input/vt-session.js +0 -168
- package/dist/ui/messages.js +0 -59
- package/dist/ui/model-manager/model-manager-state.js +0 -118
- package/dist/ui/model-manager/model-manager-ui.js +0 -194
- package/dist/ui/prompt/prompt-box.js +0 -78
- package/dist/ui/prompt/prompt-composer.js +0 -498
- package/dist/ui/spinner.js +0 -76
- package/dist/ui/title-box.js +0 -388
- package/dist/ui/tui/app.js +0 -6
- package/dist/ui/tui/autocomplete.js +0 -85
- package/dist/ui/tui/constants.js +0 -18
- package/dist/ui/tui/history.js +0 -29
- package/dist/ui/tui/layout.js +0 -341
- package/dist/ui/tui/runtime-core.js +0 -2584
- package/dist/ui/tui/runtime-utils.js +0 -53
- package/dist/ui/tui/start-tui.js +0 -54
- package/dist/ui/tui/startup.js +0 -56
- package/dist/version.js +0 -51
- package/dist/vt-session.js +0 -168
- package/install.sh +0 -457
package/dist/input-engine.js
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/** Raw-stdin input parser for deterministic prompt editing actions. */
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.InputEngine = void 0;
|
|
5
|
-
exports.parseCsiSequence = parseCsiSequence;
|
|
6
|
-
const node_string_decoder_1 = require("node:string_decoder");
|
|
7
|
-
function parseInteger(value) {
|
|
8
|
-
const parsed = Number(value);
|
|
9
|
-
return Number.isInteger(parsed) ? parsed : null;
|
|
10
|
-
}
|
|
11
|
-
function isEnterKeyCode(code) {
|
|
12
|
-
return code === 1 || code === 10 || code === 13 || code === 57414;
|
|
13
|
-
}
|
|
14
|
-
function toEnterAction(modifier) {
|
|
15
|
-
return modifier > 1 ? { type: "newline" } : { type: "submit" };
|
|
16
|
-
}
|
|
17
|
-
function parseEnterActionFromCsiBody(body, final) {
|
|
18
|
-
if (final === "u" || final === "~" || final === "M") {
|
|
19
|
-
const keyOnly = body.match(/^(\d+)$/);
|
|
20
|
-
if (keyOnly) {
|
|
21
|
-
const keyCode = parseInteger(keyOnly[1] ?? "");
|
|
22
|
-
if (keyCode !== null && isEnterKeyCode(keyCode)) {
|
|
23
|
-
return { type: "submit" };
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
const keyAndModifier = body.match(/^(\d+);(\d+)$/);
|
|
27
|
-
if (keyAndModifier) {
|
|
28
|
-
const keyCode = parseInteger(keyAndModifier[1] ?? "");
|
|
29
|
-
const modifier = parseInteger(keyAndModifier[2] ?? "");
|
|
30
|
-
if (keyCode !== null && modifier !== null && isEnterKeyCode(keyCode) && modifier >= 1) {
|
|
31
|
-
return toEnterAction(modifier);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if (final === "~") {
|
|
36
|
-
const legacyModifyOtherKeys = body.match(/^27;(\d+);(\d+)$/);
|
|
37
|
-
if (legacyModifyOtherKeys) {
|
|
38
|
-
const first = parseInteger(legacyModifyOtherKeys[1] ?? "");
|
|
39
|
-
const second = parseInteger(legacyModifyOtherKeys[2] ?? "");
|
|
40
|
-
if (first === null || second === null) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
if (isEnterKeyCode(first) && second >= 1) {
|
|
44
|
-
return toEnterAction(second);
|
|
45
|
-
}
|
|
46
|
-
if (isEnterKeyCode(second) && first >= 1) {
|
|
47
|
-
return toEnterAction(first);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
function parseMouseActionFromCsiBody(body, final) {
|
|
54
|
-
if (final !== "M" && final !== "m") {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
if (!body.startsWith("<")) {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
const params = body.slice(1).split(";");
|
|
61
|
-
const buttonCode = parseInteger(params[0] ?? "");
|
|
62
|
-
if (buttonCode === null) {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
// SGR mouse wheel encodes bit 6 for wheel + bit 0 for direction.
|
|
66
|
-
// Modifier bits may be present (shift/alt/ctrl), so match by bitmask.
|
|
67
|
-
if ((buttonCode & 0x40) !== 0) {
|
|
68
|
-
return (buttonCode & 0x01) === 0
|
|
69
|
-
? { type: "scroll-line-up" }
|
|
70
|
-
: { type: "scroll-line-down" };
|
|
71
|
-
}
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
function parseCsiSequence(sequence) {
|
|
75
|
-
if (!sequence.startsWith("\x1b[")) {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
if (sequence.length < 3) {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
const final = sequence[sequence.length - 1] ?? "";
|
|
82
|
-
const body = sequence.slice(2, -1);
|
|
83
|
-
const enterAction = parseEnterActionFromCsiBody(body, final);
|
|
84
|
-
if (enterAction) {
|
|
85
|
-
return enterAction;
|
|
86
|
-
}
|
|
87
|
-
const mouseAction = parseMouseActionFromCsiBody(body, final);
|
|
88
|
-
if (mouseAction) {
|
|
89
|
-
return mouseAction;
|
|
90
|
-
}
|
|
91
|
-
if (final === "A")
|
|
92
|
-
return { type: "move-up" };
|
|
93
|
-
if (final === "B")
|
|
94
|
-
return { type: "move-down" };
|
|
95
|
-
if (final === "C")
|
|
96
|
-
return { type: "move-right" };
|
|
97
|
-
if (final === "D")
|
|
98
|
-
return { type: "move-left" };
|
|
99
|
-
if (final === "H")
|
|
100
|
-
return { type: "home" };
|
|
101
|
-
if (final === "F")
|
|
102
|
-
return { type: "end" };
|
|
103
|
-
if (final === "Z")
|
|
104
|
-
return { type: "tab" };
|
|
105
|
-
if (final === "~") {
|
|
106
|
-
const firstParam = body.split(";")[0] ?? "";
|
|
107
|
-
if (firstParam === "1" || firstParam === "7")
|
|
108
|
-
return { type: "home" };
|
|
109
|
-
if (firstParam === "4" || firstParam === "8")
|
|
110
|
-
return { type: "end" };
|
|
111
|
-
if (firstParam === "3")
|
|
112
|
-
return { type: "delete" };
|
|
113
|
-
if (firstParam === "5")
|
|
114
|
-
return { type: "scroll-page-up" };
|
|
115
|
-
if (firstParam === "6")
|
|
116
|
-
return { type: "scroll-page-down" };
|
|
117
|
-
}
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
function isCsiFinalByte(byte) {
|
|
121
|
-
return byte >= 0x40 && byte <= 0x7e;
|
|
122
|
-
}
|
|
123
|
-
function parseEscapeSequence(buffer, startIndex) {
|
|
124
|
-
if (startIndex + 1 >= buffer.length) {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
const second = buffer[startIndex + 1] ?? 0;
|
|
128
|
-
if (second === 0x5b) {
|
|
129
|
-
let index = startIndex + 2;
|
|
130
|
-
while (index < buffer.length) {
|
|
131
|
-
const byte = buffer[index] ?? 0;
|
|
132
|
-
if (isCsiFinalByte(byte)) {
|
|
133
|
-
const sequence = buffer.subarray(startIndex, index + 1).toString("latin1");
|
|
134
|
-
return { action: parseCsiSequence(sequence), nextIndex: index + 1 };
|
|
135
|
-
}
|
|
136
|
-
index += 1;
|
|
137
|
-
}
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
if (second === 0x4f) {
|
|
141
|
-
if (startIndex + 2 >= buffer.length) {
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
const third = buffer[startIndex + 2] ?? 0;
|
|
145
|
-
if (third === 0x4d) {
|
|
146
|
-
return { action: { type: "submit" }, nextIndex: startIndex + 3 };
|
|
147
|
-
}
|
|
148
|
-
return { action: null, nextIndex: startIndex + 3 };
|
|
149
|
-
}
|
|
150
|
-
if (second === 0x0d || second === 0x0a) {
|
|
151
|
-
return { action: { type: "newline" }, nextIndex: startIndex + 2 };
|
|
152
|
-
}
|
|
153
|
-
return { action: null, nextIndex: startIndex + 2 };
|
|
154
|
-
}
|
|
155
|
-
class InputEngine {
|
|
156
|
-
pending = Buffer.alloc(0);
|
|
157
|
-
decoder = new node_string_decoder_1.StringDecoder("utf8");
|
|
158
|
-
reset() {
|
|
159
|
-
this.pending = Buffer.alloc(0);
|
|
160
|
-
this.decoder.end();
|
|
161
|
-
}
|
|
162
|
-
pushChunk(chunk) {
|
|
163
|
-
const incoming = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "latin1");
|
|
164
|
-
if (incoming.length === 0) {
|
|
165
|
-
return [];
|
|
166
|
-
}
|
|
167
|
-
this.pending =
|
|
168
|
-
this.pending.length > 0 ? Buffer.concat([this.pending, incoming]) : Buffer.from(incoming);
|
|
169
|
-
const actions = [];
|
|
170
|
-
let index = 0;
|
|
171
|
-
let textStart = null;
|
|
172
|
-
const flushText = (endIndex) => {
|
|
173
|
-
if (textStart === null || endIndex <= textStart) {
|
|
174
|
-
textStart = null;
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
const text = this.decoder.write(this.pending.subarray(textStart, endIndex));
|
|
178
|
-
if (text.length > 0) {
|
|
179
|
-
actions.push({ type: "insert", text });
|
|
180
|
-
}
|
|
181
|
-
textStart = null;
|
|
182
|
-
};
|
|
183
|
-
while (index < this.pending.length) {
|
|
184
|
-
const byte = this.pending[index] ?? 0;
|
|
185
|
-
if (byte === 0x1b) {
|
|
186
|
-
flushText(index);
|
|
187
|
-
const parsed = parseEscapeSequence(this.pending, index);
|
|
188
|
-
if (!parsed) {
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
if (parsed.action) {
|
|
192
|
-
actions.push(parsed.action);
|
|
193
|
-
}
|
|
194
|
-
index = parsed.nextIndex;
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
if (byte === 0x03) {
|
|
198
|
-
flushText(index);
|
|
199
|
-
actions.push({ type: "cancel" });
|
|
200
|
-
index += 1;
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
if (byte === 0x7f || byte === 0x08) {
|
|
204
|
-
flushText(index);
|
|
205
|
-
actions.push({ type: "backspace" });
|
|
206
|
-
index += 1;
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
if (byte === 0x0d) {
|
|
210
|
-
flushText(index);
|
|
211
|
-
actions.push({ type: "submit" });
|
|
212
|
-
index += 1;
|
|
213
|
-
if (this.pending[index] === 0x0a) {
|
|
214
|
-
index += 1;
|
|
215
|
-
}
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
if (byte === 0x0a) {
|
|
219
|
-
flushText(index);
|
|
220
|
-
actions.push({ type: "newline" });
|
|
221
|
-
index += 1;
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
|
-
if (byte === 0x09) {
|
|
225
|
-
flushText(index);
|
|
226
|
-
actions.push({ type: "tab" });
|
|
227
|
-
index += 1;
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
if (byte < 0x20) {
|
|
231
|
-
flushText(index);
|
|
232
|
-
index += 1;
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
if (textStart === null) {
|
|
236
|
-
textStart = index;
|
|
237
|
-
}
|
|
238
|
-
index += 1;
|
|
239
|
-
}
|
|
240
|
-
// Treat lone ESC as cancel (close modal/back) when it is not part of a CSI sequence.
|
|
241
|
-
if (index < this.pending.length && this.pending[index] === 0x1b && this.pending.length - index === 1) {
|
|
242
|
-
actions.push({ type: "cancel" });
|
|
243
|
-
index += 1;
|
|
244
|
-
}
|
|
245
|
-
flushText(index);
|
|
246
|
-
this.pending = this.pending.subarray(index);
|
|
247
|
-
return actions;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
exports.InputEngine = InputEngine;
|
package/dist/llama-client.js
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LlamaClient = void 0;
|
|
4
|
-
const CHAT_COMPLETIONS_PATH = "/v1/chat/completions";
|
|
5
|
-
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
6
|
-
function isRecord(value) {
|
|
7
|
-
return typeof value === "object" && value !== null;
|
|
8
|
-
}
|
|
9
|
-
function normalizeBaseUrl(baseUrl) {
|
|
10
|
-
return baseUrl.replace(/\/+$/, "");
|
|
11
|
-
}
|
|
12
|
-
function toErrorMessage(error) {
|
|
13
|
-
return error instanceof Error ? error.message : String(error);
|
|
14
|
-
}
|
|
15
|
-
function extractFirstChoice(payload) {
|
|
16
|
-
if (!isRecord(payload)) {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
const choices = payload["choices"];
|
|
20
|
-
if (!Array.isArray(choices) || choices.length === 0) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
const first = choices[0];
|
|
24
|
-
return isRecord(first) ? first : null;
|
|
25
|
-
}
|
|
26
|
-
function extractMessageContent(choice) {
|
|
27
|
-
const message = choice["message"];
|
|
28
|
-
if (isRecord(message) && typeof message["content"] === "string") {
|
|
29
|
-
return message["content"];
|
|
30
|
-
}
|
|
31
|
-
return "";
|
|
32
|
-
}
|
|
33
|
-
function extractDeltaContent(choice) {
|
|
34
|
-
const delta = choice["delta"];
|
|
35
|
-
if (isRecord(delta) && typeof delta["content"] === "string") {
|
|
36
|
-
return delta["content"];
|
|
37
|
-
}
|
|
38
|
-
return "";
|
|
39
|
-
}
|
|
40
|
-
function parseStreamLine(line) {
|
|
41
|
-
if (!line.startsWith("data:")) {
|
|
42
|
-
return { done: false, token: "" };
|
|
43
|
-
}
|
|
44
|
-
const payload = line.slice("data:".length).trim();
|
|
45
|
-
if (payload.length === 0) {
|
|
46
|
-
return { done: false, token: "" };
|
|
47
|
-
}
|
|
48
|
-
if (payload === "[DONE]") {
|
|
49
|
-
return { done: true, token: "" };
|
|
50
|
-
}
|
|
51
|
-
let parsed;
|
|
52
|
-
try {
|
|
53
|
-
parsed = JSON.parse(payload);
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
throw new Error(`Failed to parse streaming payload: ${payload.slice(0, 200)}`);
|
|
57
|
-
}
|
|
58
|
-
const choice = extractFirstChoice(parsed);
|
|
59
|
-
const usage = extractUsageTotals(parsed);
|
|
60
|
-
if (!choice) {
|
|
61
|
-
return { done: false, token: "", usage };
|
|
62
|
-
}
|
|
63
|
-
const token = extractDeltaContent(choice) || extractMessageContent(choice);
|
|
64
|
-
return { done: false, token, usage };
|
|
65
|
-
}
|
|
66
|
-
function extractUsageTotals(payload) {
|
|
67
|
-
if (!isRecord(payload)) {
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
const usage = payload["usage"];
|
|
71
|
-
if (!isRecord(usage)) {
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
74
|
-
const promptTokens = usage["prompt_tokens"];
|
|
75
|
-
const completionTokens = usage["completion_tokens"];
|
|
76
|
-
const totalTokens = usage["total_tokens"];
|
|
77
|
-
if (typeof promptTokens !== "number" ||
|
|
78
|
-
typeof completionTokens !== "number" ||
|
|
79
|
-
typeof totalTokens !== "number") {
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
if (promptTokens < 0 || completionTokens < 0 || totalTokens < 0) {
|
|
83
|
-
return undefined;
|
|
84
|
-
}
|
|
85
|
-
return { promptTokens, completionTokens, totalTokens };
|
|
86
|
-
}
|
|
87
|
-
async function readErrorBody(response) {
|
|
88
|
-
try {
|
|
89
|
-
return (await response.text()).trim();
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
return "";
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
class LlamaClient {
|
|
96
|
-
baseUrl;
|
|
97
|
-
model;
|
|
98
|
-
timeoutMs;
|
|
99
|
-
fetchImpl;
|
|
100
|
-
constructor(options) {
|
|
101
|
-
this.baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
102
|
-
this.model = options.model.trim();
|
|
103
|
-
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
104
|
-
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
105
|
-
}
|
|
106
|
-
setModel(model) {
|
|
107
|
-
const next = model.trim();
|
|
108
|
-
if (next.length > 0) {
|
|
109
|
-
this.model = next;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
getModel() {
|
|
113
|
-
return this.model;
|
|
114
|
-
}
|
|
115
|
-
async chat(messages, model = this.model) {
|
|
116
|
-
const payload = this.buildPayload(messages, model, false);
|
|
117
|
-
const response = await this.requestCompletion(payload);
|
|
118
|
-
const parsed = await response.json();
|
|
119
|
-
const choice = extractFirstChoice(parsed);
|
|
120
|
-
if (!choice) {
|
|
121
|
-
throw new Error("Chat completion response did not include choices.");
|
|
122
|
-
}
|
|
123
|
-
const content = extractMessageContent(choice);
|
|
124
|
-
if (content.length === 0) {
|
|
125
|
-
throw new Error("Chat completion response did not include assistant content.");
|
|
126
|
-
}
|
|
127
|
-
return {
|
|
128
|
-
text: content,
|
|
129
|
-
usage: extractUsageTotals(parsed)
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
async streamChat(messages, handlers, model = this.model) {
|
|
133
|
-
const payload = this.buildPayload(messages, model, true);
|
|
134
|
-
const response = await this.requestCompletion(payload);
|
|
135
|
-
if (!response.body) {
|
|
136
|
-
throw new Error("Streaming response body is unavailable.");
|
|
137
|
-
}
|
|
138
|
-
const reader = response.body.getReader();
|
|
139
|
-
const decoder = new TextDecoder();
|
|
140
|
-
let buffer = "";
|
|
141
|
-
let content = "";
|
|
142
|
-
let streamDone = false;
|
|
143
|
-
let usage;
|
|
144
|
-
try {
|
|
145
|
-
while (!streamDone) {
|
|
146
|
-
const { value, done } = await reader.read();
|
|
147
|
-
if (done) {
|
|
148
|
-
break;
|
|
149
|
-
}
|
|
150
|
-
buffer += decoder.decode(value, { stream: true });
|
|
151
|
-
const lines = buffer.split(/\r?\n/);
|
|
152
|
-
buffer = lines.pop() ?? "";
|
|
153
|
-
for (const line of lines) {
|
|
154
|
-
const result = parseStreamLine(line.trim());
|
|
155
|
-
if (result.usage) {
|
|
156
|
-
usage = result.usage;
|
|
157
|
-
}
|
|
158
|
-
if (result.token.length > 0) {
|
|
159
|
-
content += result.token;
|
|
160
|
-
handlers.onToken(result.token);
|
|
161
|
-
}
|
|
162
|
-
if (result.done) {
|
|
163
|
-
streamDone = true;
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const finalChunk = decoder.decode();
|
|
169
|
-
if (finalChunk.length > 0) {
|
|
170
|
-
buffer += finalChunk;
|
|
171
|
-
}
|
|
172
|
-
if (!streamDone && buffer.trim().length > 0) {
|
|
173
|
-
const result = parseStreamLine(buffer.trim());
|
|
174
|
-
if (result.usage) {
|
|
175
|
-
usage = result.usage;
|
|
176
|
-
}
|
|
177
|
-
if (result.token.length > 0) {
|
|
178
|
-
content += result.token;
|
|
179
|
-
handlers.onToken(result.token);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return { text: content, usage };
|
|
183
|
-
}
|
|
184
|
-
finally {
|
|
185
|
-
reader.releaseLock();
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
buildPayload(messages, model, stream) {
|
|
189
|
-
return {
|
|
190
|
-
model: model.trim() || this.model,
|
|
191
|
-
messages,
|
|
192
|
-
stream
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
async requestCompletion(payload) {
|
|
196
|
-
const controller = new AbortController();
|
|
197
|
-
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
198
|
-
const endpoint = `${this.baseUrl}${CHAT_COMPLETIONS_PATH}`;
|
|
199
|
-
let response;
|
|
200
|
-
try {
|
|
201
|
-
response = await this.fetchImpl(endpoint, {
|
|
202
|
-
method: "POST",
|
|
203
|
-
headers: {
|
|
204
|
-
"content-type": "application/json"
|
|
205
|
-
},
|
|
206
|
-
body: JSON.stringify(payload),
|
|
207
|
-
signal: controller.signal
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
catch (error) {
|
|
211
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
212
|
-
throw new Error(`llama.cpp request timed out after ${this.timeoutMs}ms.`);
|
|
213
|
-
}
|
|
214
|
-
throw new Error(`Failed to connect to llama.cpp at ${this.baseUrl}: ${toErrorMessage(error)}`);
|
|
215
|
-
}
|
|
216
|
-
finally {
|
|
217
|
-
clearTimeout(timeout);
|
|
218
|
-
}
|
|
219
|
-
if (!response.ok) {
|
|
220
|
-
const details = await readErrorBody(response);
|
|
221
|
-
const suffix = details.length > 0 ? `: ${details}` : "";
|
|
222
|
-
throw new Error(`llama.cpp request failed (${response.status} ${response.statusText})${suffix}`);
|
|
223
|
-
}
|
|
224
|
-
return response;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
exports.LlamaClient = LlamaClient;
|