quassel 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.
@@ -0,0 +1,478 @@
1
+ // packages/quassel/src/server/openai.ts
2
+ async function streamChatCompletion(config, messages, signal, callbacks) {
3
+ const body = {
4
+ model: config.model,
5
+ messages,
6
+ stream: true
7
+ };
8
+ if (config.temperature !== void 0) body.temperature = config.temperature;
9
+ if (config.topP !== void 0) body.top_p = config.topP;
10
+ if (config.maxTokens !== void 0) body.max_tokens = config.maxTokens;
11
+ if (config.tools && config.tools.length > 0) {
12
+ body.tools = config.tools.map((tool) => ({
13
+ type: "function",
14
+ function: { name: tool.name, description: tool.description, parameters: tool.parameters }
15
+ }));
16
+ }
17
+ const response = await fetch(`${config.baseUrl}/chat/completions`, {
18
+ method: "POST",
19
+ headers: {
20
+ "Content-Type": "application/json",
21
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {},
22
+ ...config.headers
23
+ },
24
+ body: JSON.stringify(body),
25
+ signal
26
+ });
27
+ if (!response.ok || !response.body) {
28
+ const detail = await response.text().catch(() => "");
29
+ throw new Error(`Backend antwortet mit ${response.status}: ${detail.slice(0, 400)}`);
30
+ }
31
+ const denkFilter = new ThinkTagFilter(callbacks);
32
+ const toolCalls = /* @__PURE__ */ new Map();
33
+ let content = "";
34
+ let finishReason;
35
+ const reader = response.body.getReader();
36
+ const decoder = new TextDecoder();
37
+ let puffer = "";
38
+ for (; ; ) {
39
+ const { done, value } = await reader.read();
40
+ if (done) {
41
+ break;
42
+ }
43
+ puffer += decoder.decode(value, { stream: true });
44
+ const zeilen = puffer.split("\n");
45
+ puffer = zeilen.pop() ?? "";
46
+ for (const zeile of zeilen) {
47
+ if (!zeile.startsWith("data:")) {
48
+ continue;
49
+ }
50
+ const daten = zeile.slice(5).trim();
51
+ if (!daten || daten === "[DONE]") {
52
+ continue;
53
+ }
54
+ const chunk = JSON.parse(daten);
55
+ const choice = chunk.choices?.[0];
56
+ if (!choice) {
57
+ continue;
58
+ }
59
+ if (choice.finish_reason) {
60
+ finishReason = choice.finish_reason;
61
+ }
62
+ const delta = choice.delta ?? {};
63
+ const reasoning = delta.reasoning ?? delta.reasoning_content;
64
+ if (typeof reasoning === "string" && reasoning.length > 0) {
65
+ callbacks.onThinking(reasoning);
66
+ }
67
+ if (typeof delta.content === "string" && delta.content.length > 0) {
68
+ content += denkFilter.push(delta.content);
69
+ }
70
+ for (const teil of delta.tool_calls ?? []) {
71
+ const index = teil.index ?? 0;
72
+ const bisher = toolCalls.get(index) ?? {
73
+ id: teil.id ?? `call_${index}`,
74
+ type: "function",
75
+ function: { name: "", arguments: "" }
76
+ };
77
+ if (teil.id) bisher.id = teil.id;
78
+ if (teil.function?.name) bisher.function.name += teil.function.name;
79
+ if (teil.function?.arguments) bisher.function.arguments += teil.function.arguments;
80
+ toolCalls.set(index, bisher);
81
+ }
82
+ }
83
+ }
84
+ content += denkFilter.flush();
85
+ return {
86
+ content,
87
+ toolCalls: [...toolCalls.entries()].sort(([a], [b]) => a - b).map(([, call]) => call),
88
+ finishReason
89
+ };
90
+ }
91
+ var ThinkTagFilter = class {
92
+ constructor(callbacks) {
93
+ this.callbacks = callbacks;
94
+ }
95
+ callbacks;
96
+ inThink = false;
97
+ rest = "";
98
+ push(delta) {
99
+ this.rest += delta;
100
+ let heraus = "";
101
+ for (; ; ) {
102
+ const tag = this.inThink ? "</think>" : "<think>";
103
+ const pos = this.rest.indexOf(tag);
104
+ if (pos === -1) {
105
+ const sicher = this.sicherAusgebbar(this.rest, tag);
106
+ heraus += this.gebeAus(this.rest.slice(0, sicher));
107
+ this.rest = this.rest.slice(sicher);
108
+ return heraus;
109
+ }
110
+ heraus += this.gebeAus(this.rest.slice(0, pos));
111
+ this.rest = this.rest.slice(pos + tag.length);
112
+ this.inThink = !this.inThink;
113
+ }
114
+ }
115
+ flush() {
116
+ const heraus = this.gebeAus(this.rest);
117
+ this.rest = "";
118
+ return heraus;
119
+ }
120
+ /* Ein angebrochenes Tag am Chunk-Ende bleibt im Puffer, bis der Rest eintrifft. */
121
+ sicherAusgebbar(text, tag) {
122
+ for (let laenge = tag.length - 1; laenge > 0; laenge--) {
123
+ if (text.endsWith(tag.slice(0, laenge))) {
124
+ return text.length - laenge;
125
+ }
126
+ }
127
+ return text.length;
128
+ }
129
+ gebeAus(text) {
130
+ if (!text) {
131
+ return "";
132
+ }
133
+ if (this.inThink) {
134
+ this.callbacks.onThinking(text);
135
+ return "";
136
+ }
137
+ this.callbacks.onText(text);
138
+ return text;
139
+ }
140
+ };
141
+
142
+ // packages/quassel/src/server/session.ts
143
+ var MAX_RUNDEN = 12;
144
+ var AgentSession = class {
145
+ constructor(id, config, store, initial) {
146
+ this.id = id;
147
+ this.config = config;
148
+ this.store = store;
149
+ if (initial) {
150
+ this.events.push(...initial.events);
151
+ this.history.push(...initial.history);
152
+ } else if (config.systemPrompt) {
153
+ this.history.push({ role: "system", content: config.systemPrompt });
154
+ }
155
+ }
156
+ id;
157
+ config;
158
+ store;
159
+ events = [];
160
+ history = [];
161
+ listeners = /* @__PURE__ */ new Set();
162
+ steering = [];
163
+ abort;
164
+ _running = false;
165
+ get running() {
166
+ return this._running;
167
+ }
168
+ subscribe(listener) {
169
+ listener({ kind: "reset" });
170
+ for (const event of this.events) {
171
+ listener(event);
172
+ }
173
+ listener({ kind: "status", running: this._running });
174
+ this.listeners.add(listener);
175
+ return () => this.listeners.delete(listener);
176
+ }
177
+ send(text) {
178
+ if (this._running) {
179
+ this.emit({ kind: "user", text });
180
+ this.steering.push(text);
181
+ return;
182
+ }
183
+ void this.run(text);
184
+ }
185
+ stop() {
186
+ if (!this._running) {
187
+ return;
188
+ }
189
+ this.steering.length = 0;
190
+ this.abort?.abort();
191
+ }
192
+ async run(text) {
193
+ this._running = true;
194
+ this.abort = new AbortController();
195
+ const signal = this.abort.signal;
196
+ this.emit({ kind: "status", running: true });
197
+ this.emit({ kind: "user", text });
198
+ this.history.push({ role: "user", content: text });
199
+ try {
200
+ for (let runde = 0; runde < MAX_RUNDEN; runde++) {
201
+ const result = await streamChatCompletion(this.config, this.history, signal, {
202
+ onText: (delta) => this.emit({ kind: "text", delta }),
203
+ onThinking: (delta) => this.emit({ kind: "thinking", delta })
204
+ });
205
+ this.history.push({
206
+ role: "assistant",
207
+ content: result.content || null,
208
+ ...result.toolCalls.length > 0 ? { tool_calls: result.toolCalls } : {}
209
+ });
210
+ for (const call of result.toolCalls) {
211
+ this.emit({ kind: "tool", id: call.id, name: call.function.name, arguments: call.function.arguments });
212
+ const antwort = await this.runTool(call.function.name, call.function.arguments, call.id);
213
+ this.history.push({ role: "tool", content: antwort.result, tool_call_id: call.id });
214
+ }
215
+ while (this.steering.length > 0) {
216
+ this.history.push({ role: "user", content: this.steering.shift() });
217
+ }
218
+ const fertig = result.toolCalls.length === 0 && this.history[this.history.length - 1]?.role !== "user";
219
+ if (fertig) {
220
+ break;
221
+ }
222
+ }
223
+ } catch (error) {
224
+ if (signal.aborted) {
225
+ this.emit({ kind: "system", text: "Abgebrochen." });
226
+ } else {
227
+ this.emit({ kind: "system", text: error instanceof Error ? error.message : String(error) });
228
+ }
229
+ } finally {
230
+ this._running = false;
231
+ this.abort = void 0;
232
+ this.emit({ kind: "turn-done" });
233
+ this.emit({ kind: "status", running: false });
234
+ await this.persist();
235
+ }
236
+ }
237
+ async runTool(name, argsText, callId) {
238
+ const tool = this.config.tools?.find((eintrag) => eintrag.name === name);
239
+ if (!tool) {
240
+ const fehler = `Unbekanntes Werkzeug: ${name}`;
241
+ this.emit({ kind: "tool-result", id: callId, result: fehler, isError: true });
242
+ return { result: fehler };
243
+ }
244
+ try {
245
+ const args = argsText.trim() === "" ? {} : JSON.parse(argsText);
246
+ const result = String(await tool.run(args));
247
+ this.emit({ kind: "tool-result", id: callId, result });
248
+ return { result };
249
+ } catch (error) {
250
+ const fehler = error instanceof Error ? error.message : String(error);
251
+ this.emit({ kind: "tool-result", id: callId, result: fehler, isError: true });
252
+ return { result: `Fehler: ${fehler}` };
253
+ }
254
+ }
255
+ emit(event) {
256
+ if (event.kind !== "status") {
257
+ this.events.push(event);
258
+ }
259
+ for (const listener of this.listeners) {
260
+ listener(event);
261
+ }
262
+ }
263
+ async persist() {
264
+ if (!this.store) {
265
+ return;
266
+ }
267
+ await this.store.save({
268
+ id: this.id,
269
+ events: this.events,
270
+ history: this.history,
271
+ updatedAt: Date.now()
272
+ });
273
+ }
274
+ };
275
+
276
+ // packages/quassel/src/server/store.ts
277
+ import { mkdir, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
278
+ import { join } from "node:path";
279
+ function titelAus(state) {
280
+ const erste = state.events.find((event) => event.kind === "user");
281
+ const text = erste && erste.kind === "user" ? erste.text : "Neue Unterhaltung";
282
+ return text.length > 80 ? `${text.slice(0, 80)} ...` : text;
283
+ }
284
+ var MemorySessionStore = class {
285
+ states = /* @__PURE__ */ new Map();
286
+ async load(id) {
287
+ return this.states.get(id);
288
+ }
289
+ async save(state) {
290
+ this.states.set(state.id, state);
291
+ }
292
+ async list() {
293
+ return [...this.states.values()].map((state) => ({ id: state.id, title: titelAus(state), updatedAt: state.updatedAt })).sort((a, b) => b.updatedAt - a.updatedAt);
294
+ }
295
+ async delete(id) {
296
+ this.states.delete(id);
297
+ }
298
+ };
299
+ var FileSessionStore = class {
300
+ constructor(dir) {
301
+ this.dir = dir;
302
+ }
303
+ dir;
304
+ async load(id) {
305
+ try {
306
+ return JSON.parse(await readFile(this.pfad(id), "utf8"));
307
+ } catch {
308
+ return void 0;
309
+ }
310
+ }
311
+ async save(state) {
312
+ await mkdir(this.dir, { recursive: true });
313
+ const temp = `${this.pfad(state.id)}.tmp`;
314
+ await writeFile(temp, JSON.stringify(state), "utf8");
315
+ await rename(temp, this.pfad(state.id));
316
+ }
317
+ async list() {
318
+ let dateien;
319
+ try {
320
+ dateien = await readdir(this.dir);
321
+ } catch {
322
+ return [];
323
+ }
324
+ const infos = [];
325
+ for (const datei of dateien) {
326
+ if (!datei.endsWith(".json")) {
327
+ continue;
328
+ }
329
+ const state = await this.load(datei.slice(0, -5));
330
+ if (state) {
331
+ infos.push({ id: state.id, title: titelAus(state), updatedAt: state.updatedAt });
332
+ }
333
+ }
334
+ return infos.sort((a, b) => b.updatedAt - a.updatedAt);
335
+ }
336
+ async delete(id) {
337
+ await rm(this.pfad(id), { force: true });
338
+ }
339
+ pfad(id) {
340
+ return join(this.dir, `${id}.json`);
341
+ }
342
+ };
343
+
344
+ // packages/quassel/src/server/manager.ts
345
+ var ID_MUSTER = /^[A-Za-z0-9_-]{1,64}$/;
346
+ var SessionManager = class {
347
+ constructor(config, store = new MemorySessionStore()) {
348
+ this.config = config;
349
+ this.store = store;
350
+ }
351
+ config;
352
+ store;
353
+ sessions = /* @__PURE__ */ new Map();
354
+ async get(id) {
355
+ if (!ID_MUSTER.test(id)) {
356
+ throw new Error(`Ung\xFCltige Session-Id: ${id}`);
357
+ }
358
+ const vorhanden = this.sessions.get(id);
359
+ if (vorhanden) {
360
+ return vorhanden;
361
+ }
362
+ const state = await this.store.load(id);
363
+ const session = new AgentSession(id, this.config, this.store, state);
364
+ this.sessions.set(id, session);
365
+ return session;
366
+ }
367
+ async list() {
368
+ return this.store.list();
369
+ }
370
+ async delete(id) {
371
+ this.sessions.get(id)?.stop();
372
+ this.sessions.delete(id);
373
+ await this.store.delete(id);
374
+ }
375
+ };
376
+
377
+ // packages/quassel/src/server/http.ts
378
+ function createChatHandler({ manager, prefix = "/chat", cors = true }) {
379
+ return async (req, res) => {
380
+ const url = new URL(req.url ?? "/", "http://intern");
381
+ if (url.pathname !== prefix && !url.pathname.startsWith(`${prefix}/`)) {
382
+ return false;
383
+ }
384
+ if (cors) {
385
+ res.setHeader("Access-Control-Allow-Origin", "*");
386
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
387
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
388
+ }
389
+ if (req.method === "OPTIONS") {
390
+ res.writeHead(204).end();
391
+ return true;
392
+ }
393
+ const teile = url.pathname.slice(prefix.length).split("/").filter(Boolean);
394
+ try {
395
+ if (req.method === "GET" && teile.length === 1 && teile[0] === "sessions") {
396
+ antworteJson(res, 200, await manager.list());
397
+ return true;
398
+ }
399
+ if (req.method === "DELETE" && teile.length === 1) {
400
+ await manager.delete(teile[0]);
401
+ antworteJson(res, 200, { ok: true });
402
+ return true;
403
+ }
404
+ if (teile.length !== 2) {
405
+ antworteJson(res, 404, { error: "Unbekannte Route" });
406
+ return true;
407
+ }
408
+ const [id, aktion] = teile;
409
+ const session = await manager.get(id);
410
+ if (req.method === "GET" && aktion === "stream") {
411
+ res.writeHead(200, {
412
+ "Content-Type": "text/event-stream",
413
+ "Cache-Control": "no-cache",
414
+ Connection: "keep-alive"
415
+ });
416
+ const abmelden = session.subscribe((event) => {
417
+ res.write(`data: ${JSON.stringify(event)}
418
+
419
+ `);
420
+ });
421
+ const ping = setInterval(() => res.write(": ping\n\n"), 15e3);
422
+ req.on("close", () => {
423
+ clearInterval(ping);
424
+ abmelden();
425
+ });
426
+ return true;
427
+ }
428
+ if (req.method === "POST" && aktion === "send") {
429
+ const body = await leseJson(req);
430
+ const text = typeof body?.text === "string" ? body.text.trim() : "";
431
+ if (!text) {
432
+ antworteJson(res, 400, { error: "text fehlt" });
433
+ return true;
434
+ }
435
+ session.send(text);
436
+ antworteJson(res, 202, { ok: true });
437
+ return true;
438
+ }
439
+ if (req.method === "POST" && aktion === "stop") {
440
+ session.stop();
441
+ antworteJson(res, 200, { ok: true });
442
+ return true;
443
+ }
444
+ antworteJson(res, 404, { error: "Unbekannte Route" });
445
+ } catch (error) {
446
+ antworteJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
447
+ }
448
+ return true;
449
+ };
450
+ }
451
+ function antworteJson(res, status, body) {
452
+ res.writeHead(status, { "Content-Type": "application/json" });
453
+ res.end(JSON.stringify(body));
454
+ }
455
+ async function leseJson(req) {
456
+ const stuecke = [];
457
+ for await (const stueck of req) {
458
+ stuecke.push(stueck);
459
+ }
460
+ const text = Buffer.concat(stuecke).toString("utf8");
461
+ if (!text) {
462
+ return void 0;
463
+ }
464
+ try {
465
+ return JSON.parse(text);
466
+ } catch {
467
+ return void 0;
468
+ }
469
+ }
470
+ export {
471
+ AgentSession,
472
+ FileSessionStore,
473
+ MemorySessionStore,
474
+ SessionManager,
475
+ createChatHandler,
476
+ streamChatCompletion
477
+ };
478
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/server/openai.ts", "../../src/server/session.ts", "../../src/server/store.ts", "../../src/server/manager.ts", "../../src/server/http.ts"],
4
+ "sourcesContent": ["import { AgentConfig, OpenAiMessage, OpenAiToolCall } from \"./types\";\n\nexport interface StreamCallbacks {\n onText: (delta: string) => void;\n onThinking: (delta: string) => void;\n}\n\nexport interface StreamResult {\n content: string;\n toolCalls: OpenAiToolCall[];\n finishReason: string | undefined;\n}\n\ninterface DeltaToolCall {\n index?: number;\n id?: string;\n function?: { name?: string; arguments?: string };\n}\n\n/**\n * Ein Streaming-Aufruf gegen /chat/completions. Denk-Anteile kommen je nach Backend als\n * eigenes reasoning-Feld (OpenRouter) oder als <think>-Tags im Content (Ollama) - beides\n * landet in onThinking.\n */\nexport async function streamChatCompletion(\n config: AgentConfig,\n messages: OpenAiMessage[],\n signal: AbortSignal,\n callbacks: StreamCallbacks,\n): Promise<StreamResult> {\n const body: Record<string, unknown> = {\n model: config.model,\n messages,\n stream: true,\n };\n if (config.temperature !== undefined) body.temperature = config.temperature;\n if (config.topP !== undefined) body.top_p = config.topP;\n if (config.maxTokens !== undefined) body.max_tokens = config.maxTokens;\n if (config.tools && config.tools.length > 0) {\n body.tools = config.tools.map((tool) => ({\n type: \"function\",\n function: { name: tool.name, description: tool.description, parameters: tool.parameters },\n }));\n }\n\n const response = await fetch(`${config.baseUrl}/chat/completions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n ...config.headers,\n },\n body: JSON.stringify(body),\n signal,\n });\n if (!response.ok || !response.body) {\n const detail = await response.text().catch(() => \"\");\n throw new Error(`Backend antwortet mit ${response.status}: ${detail.slice(0, 400)}`);\n }\n\n const denkFilter = new ThinkTagFilter(callbacks);\n const toolCalls = new Map<number, OpenAiToolCall>();\n let content = \"\";\n let finishReason: string | undefined;\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let puffer = \"\";\n for (;;) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n puffer += decoder.decode(value, { stream: true });\n const zeilen = puffer.split(\"\\n\");\n puffer = zeilen.pop() ?? \"\";\n for (const zeile of zeilen) {\n if (!zeile.startsWith(\"data:\")) {\n continue;\n }\n const daten = zeile.slice(5).trim();\n if (!daten || daten === \"[DONE]\") {\n continue;\n }\n const chunk = JSON.parse(daten);\n const choice = chunk.choices?.[0];\n if (!choice) {\n continue;\n }\n if (choice.finish_reason) {\n finishReason = choice.finish_reason;\n }\n const delta = choice.delta ?? {};\n const reasoning = delta.reasoning ?? delta.reasoning_content;\n if (typeof reasoning === \"string\" && reasoning.length > 0) {\n callbacks.onThinking(reasoning);\n }\n if (typeof delta.content === \"string\" && delta.content.length > 0) {\n content += denkFilter.push(delta.content);\n }\n for (const teil of (delta.tool_calls ?? []) as DeltaToolCall[]) {\n const index = teil.index ?? 0;\n const bisher = toolCalls.get(index) ?? {\n id: teil.id ?? `call_${index}`,\n type: \"function\" as const,\n function: { name: \"\", arguments: \"\" },\n };\n if (teil.id) bisher.id = teil.id;\n if (teil.function?.name) bisher.function.name += teil.function.name;\n if (teil.function?.arguments) bisher.function.arguments += teil.function.arguments;\n toolCalls.set(index, bisher);\n }\n }\n }\n content += denkFilter.flush();\n\n return {\n content,\n toolCalls: [...toolCalls.entries()].sort(([a], [b]) => a - b).map(([, call]) => call),\n finishReason,\n };\n}\n\n/**\n * Trennt <think>-Bloecke aus einem Delta-Strom: Denk-Text geht an onThinking, der Rest\n * wird zurueckgegeben. Tags koennen mitten in einem Chunk oder ueber Chunk-Grenzen liegen.\n */\nclass ThinkTagFilter {\n private inThink = false;\n private rest = \"\";\n\n constructor(private callbacks: StreamCallbacks) {}\n\n push(delta: string): string {\n this.rest += delta;\n let heraus = \"\";\n for (;;) {\n const tag = this.inThink ? \"</think>\" : \"<think>\";\n const pos = this.rest.indexOf(tag);\n if (pos === -1) {\n const sicher = this.sicherAusgebbar(this.rest, tag);\n heraus += this.gebeAus(this.rest.slice(0, sicher));\n this.rest = this.rest.slice(sicher);\n return heraus;\n }\n heraus += this.gebeAus(this.rest.slice(0, pos));\n this.rest = this.rest.slice(pos + tag.length);\n this.inThink = !this.inThink;\n }\n }\n\n flush(): string {\n const heraus = this.gebeAus(this.rest);\n this.rest = \"\";\n return heraus;\n }\n\n /* Ein angebrochenes Tag am Chunk-Ende bleibt im Puffer, bis der Rest eintrifft. */\n private sicherAusgebbar(text: string, tag: string): number {\n for (let laenge = tag.length - 1; laenge > 0; laenge--) {\n if (text.endsWith(tag.slice(0, laenge))) {\n return text.length - laenge;\n }\n }\n return text.length;\n }\n\n private gebeAus(text: string): string {\n if (!text) {\n return \"\";\n }\n if (this.inThink) {\n this.callbacks.onThinking(text);\n return \"\";\n }\n this.callbacks.onText(text);\n return text;\n }\n}\n", "import { ChatEvent } from \"../events\";\nimport { streamChatCompletion } from \"./openai\";\nimport { AgentConfig, OpenAiMessage, SessionState, SessionStore } from \"./types\";\n\nconst MAX_RUNDEN = 12;\n\n/**\n * Eine Unterhaltung: haelt Verlauf und Event-Log, streamt Antworten, fuehrt Tools aus.\n * Senden waehrend eines Laufs wird zum Zwischenruf und am Runden-Ende beruecksichtigt.\n * Abonnenten bekommen erst den bisherigen Verlauf, dann live.\n */\nexport class AgentSession {\n private readonly events: ChatEvent[] = [];\n private readonly history: OpenAiMessage[] = [];\n private readonly listeners = new Set<(event: ChatEvent) => void>();\n private readonly steering: string[] = [];\n private abort?: AbortController;\n private _running = false;\n\n constructor(\n readonly id: string,\n private readonly config: AgentConfig,\n private readonly store?: SessionStore,\n initial?: SessionState,\n ) {\n if (initial) {\n this.events.push(...initial.events);\n this.history.push(...initial.history);\n } else if (config.systemPrompt) {\n this.history.push({ role: \"system\", content: config.systemPrompt });\n }\n }\n\n get running(): boolean {\n return this._running;\n }\n\n subscribe(listener: (event: ChatEvent) => void): () => void {\n listener({ kind: \"reset\" });\n for (const event of this.events) {\n listener(event);\n }\n listener({ kind: \"status\", running: this._running });\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n send(text: string): void {\n if (this._running) {\n this.emit({ kind: \"user\", text });\n this.steering.push(text);\n return;\n }\n void this.run(text);\n }\n\n stop(): void {\n if (!this._running) {\n return;\n }\n this.steering.length = 0;\n this.abort?.abort();\n }\n\n private async run(text: string): Promise<void> {\n this._running = true;\n this.abort = new AbortController();\n const signal = this.abort.signal;\n this.emit({ kind: \"status\", running: true });\n this.emit({ kind: \"user\", text });\n this.history.push({ role: \"user\", content: text });\n\n try {\n for (let runde = 0; runde < MAX_RUNDEN; runde++) {\n const result = await streamChatCompletion(this.config, this.history, signal, {\n onText: (delta) => this.emit({ kind: \"text\", delta }),\n onThinking: (delta) => this.emit({ kind: \"thinking\", delta }),\n });\n this.history.push({\n role: \"assistant\",\n content: result.content || null,\n ...(result.toolCalls.length > 0 ? { tool_calls: result.toolCalls } : {}),\n });\n\n for (const call of result.toolCalls) {\n this.emit({ kind: \"tool\", id: call.id, name: call.function.name, arguments: call.function.arguments });\n const antwort = await this.runTool(call.function.name, call.function.arguments, call.id);\n this.history.push({ role: \"tool\", content: antwort.result, tool_call_id: call.id });\n }\n\n while (this.steering.length > 0) {\n this.history.push({ role: \"user\", content: this.steering.shift()! });\n }\n\n const fertig = result.toolCalls.length === 0 && this.history[this.history.length - 1]?.role !== \"user\";\n if (fertig) {\n break;\n }\n }\n } catch (error) {\n if (signal.aborted) {\n this.emit({ kind: \"system\", text: \"Abgebrochen.\" });\n } else {\n this.emit({ kind: \"system\", text: error instanceof Error ? error.message : String(error) });\n }\n } finally {\n this._running = false;\n this.abort = undefined;\n this.emit({ kind: \"turn-done\" });\n this.emit({ kind: \"status\", running: false });\n await this.persist();\n }\n }\n\n private async runTool(name: string, argsText: string, callId: string): Promise<{ result: string }> {\n const tool = this.config.tools?.find((eintrag) => eintrag.name === name);\n if (!tool) {\n const fehler = `Unbekanntes Werkzeug: ${name}`;\n this.emit({ kind: \"tool-result\", id: callId, result: fehler, isError: true });\n return { result: fehler };\n }\n try {\n const args = argsText.trim() === \"\" ? {} : JSON.parse(argsText);\n const result = String(await tool.run(args));\n this.emit({ kind: \"tool-result\", id: callId, result });\n return { result };\n } catch (error) {\n const fehler = error instanceof Error ? error.message : String(error);\n this.emit({ kind: \"tool-result\", id: callId, result: fehler, isError: true });\n return { result: `Fehler: ${fehler}` };\n }\n }\n\n private emit(event: ChatEvent): void {\n if (event.kind !== \"status\") {\n this.events.push(event);\n }\n for (const listener of this.listeners) {\n listener(event);\n }\n }\n\n private async persist(): Promise<void> {\n if (!this.store) {\n return;\n }\n await this.store.save({\n id: this.id,\n events: this.events,\n history: this.history,\n updatedAt: Date.now(),\n });\n }\n}\n", "import { mkdir, readdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { SessionInfo, SessionState, SessionStore } from \"./types\";\n\nfunction titelAus(state: SessionState): string {\n const erste = state.events.find((event) => event.kind === \"user\");\n const text = erste && erste.kind === \"user\" ? erste.text : \"Neue Unterhaltung\";\n return text.length > 80 ? `${text.slice(0, 80)} ...` : text;\n}\n\nexport class MemorySessionStore implements SessionStore {\n private readonly states = new Map<string, SessionState>();\n\n async load(id: string): Promise<SessionState | undefined> {\n return this.states.get(id);\n }\n\n async save(state: SessionState): Promise<void> {\n this.states.set(state.id, state);\n }\n\n async list(): Promise<SessionInfo[]> {\n return [...this.states.values()]\n .map((state) => ({ id: state.id, title: titelAus(state), updatedAt: state.updatedAt }))\n .sort((a, b) => b.updatedAt - a.updatedAt);\n }\n\n async delete(id: string): Promise<void> {\n this.states.delete(id);\n }\n}\n\n/** Eine JSON-Datei je Unterhaltung; geschrieben wird atomar ueber eine Temp-Datei. */\nexport class FileSessionStore implements SessionStore {\n constructor(private readonly dir: string) {}\n\n async load(id: string): Promise<SessionState | undefined> {\n try {\n return JSON.parse(await readFile(this.pfad(id), \"utf8\")) as SessionState;\n } catch {\n return undefined;\n }\n }\n\n async save(state: SessionState): Promise<void> {\n await mkdir(this.dir, { recursive: true });\n const temp = `${this.pfad(state.id)}.tmp`;\n await writeFile(temp, JSON.stringify(state), \"utf8\");\n await rename(temp, this.pfad(state.id));\n }\n\n async list(): Promise<SessionInfo[]> {\n let dateien: string[];\n try {\n dateien = await readdir(this.dir);\n } catch {\n return [];\n }\n const infos: SessionInfo[] = [];\n for (const datei of dateien) {\n if (!datei.endsWith(\".json\")) {\n continue;\n }\n const state = await this.load(datei.slice(0, -5));\n if (state) {\n infos.push({ id: state.id, title: titelAus(state), updatedAt: state.updatedAt });\n }\n }\n return infos.sort((a, b) => b.updatedAt - a.updatedAt);\n }\n\n async delete(id: string): Promise<void> {\n await rm(this.pfad(id), { force: true });\n }\n\n private pfad(id: string): string {\n return join(this.dir, `${id}.json`);\n }\n}\n", "import { AgentSession } from \"./session\";\nimport { AgentConfig, SessionInfo, SessionStore } from \"./types\";\nimport { MemorySessionStore } from \"./store\";\n\nconst ID_MUSTER = /^[A-Za-z0-9_-]{1,64}$/;\n\n/** Haelt lebende Sessions im Speicher und laedt persistierte bei Bedarf nach. */\nexport class SessionManager {\n private readonly sessions = new Map<string, AgentSession>();\n\n constructor(\n private readonly config: AgentConfig,\n private readonly store: SessionStore = new MemorySessionStore(),\n ) {}\n\n async get(id: string): Promise<AgentSession> {\n if (!ID_MUSTER.test(id)) {\n throw new Error(`Ung\u00FCltige Session-Id: ${id}`);\n }\n const vorhanden = this.sessions.get(id);\n if (vorhanden) {\n return vorhanden;\n }\n const state = await this.store.load(id);\n const session = new AgentSession(id, this.config, this.store, state);\n this.sessions.set(id, session);\n return session;\n }\n\n async list(): Promise<SessionInfo[]> {\n return this.store.list();\n }\n\n async delete(id: string): Promise<void> {\n this.sessions.get(id)?.stop();\n this.sessions.delete(id);\n await this.store.delete(id);\n }\n}\n", "import { IncomingMessage, ServerResponse } from \"node:http\";\nimport { ChatSessionProvider } from \"./types\";\n\nexport interface ChatHandlerOptions {\n manager: ChatSessionProvider;\n prefix?: string;\n cors?: boolean;\n}\n\n/**\n * Framework-freier HTTP-Adapter. Routen unter dem Prefix (default /chat):\n * GET /:id/stream SSE - erst Verlauf, dann live, mit Keepalive-Pings\n * POST /:id/send { text } - startet einen Lauf oder funkt dazwischen\n * POST /:id/stop bricht den laufenden Turn ab\n * GET /sessions persistierte Unterhaltungen\n * DELETE /:id loescht eine Unterhaltung\n * Rueckgabe true heisst: Anfrage wurde behandelt.\n */\nexport function createChatHandler({ manager, prefix = \"/chat\", cors = true }: ChatHandlerOptions) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<boolean> => {\n const url = new URL(req.url ?? \"/\", \"http://intern\");\n if (url.pathname !== prefix && !url.pathname.startsWith(`${prefix}/`)) {\n return false;\n }\n if (cors) {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, DELETE, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\");\n }\n if (req.method === \"OPTIONS\") {\n res.writeHead(204).end();\n return true;\n }\n\n const teile = url.pathname.slice(prefix.length).split(\"/\").filter(Boolean);\n try {\n if (req.method === \"GET\" && teile.length === 1 && teile[0] === \"sessions\") {\n antworteJson(res, 200, await manager.list());\n return true;\n }\n if (req.method === \"DELETE\" && teile.length === 1) {\n await manager.delete(teile[0]);\n antworteJson(res, 200, { ok: true });\n return true;\n }\n if (teile.length !== 2) {\n antworteJson(res, 404, { error: \"Unbekannte Route\" });\n return true;\n }\n const [id, aktion] = teile;\n const session = await manager.get(id);\n\n if (req.method === \"GET\" && aktion === \"stream\") {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n const abmelden = session.subscribe((event) => {\n res.write(`data: ${JSON.stringify(event)}\\n\\n`);\n });\n const ping = setInterval(() => res.write(\": ping\\n\\n\"), 15000);\n req.on(\"close\", () => {\n clearInterval(ping);\n abmelden();\n });\n return true;\n }\n if (req.method === \"POST\" && aktion === \"send\") {\n const body = await leseJson(req);\n const text = typeof body?.text === \"string\" ? body.text.trim() : \"\";\n if (!text) {\n antworteJson(res, 400, { error: \"text fehlt\" });\n return true;\n }\n session.send(text);\n antworteJson(res, 202, { ok: true });\n return true;\n }\n if (req.method === \"POST\" && aktion === \"stop\") {\n session.stop();\n antworteJson(res, 200, { ok: true });\n return true;\n }\n antworteJson(res, 404, { error: \"Unbekannte Route\" });\n } catch (error) {\n antworteJson(res, 500, { error: error instanceof Error ? error.message : String(error) });\n }\n return true;\n };\n}\n\nfunction antworteJson(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n\nasync function leseJson(req: IncomingMessage): Promise<Record<string, unknown> | undefined> {\n const stuecke: Buffer[] = [];\n for await (const stueck of req) {\n stuecke.push(stueck as Buffer);\n }\n const text = Buffer.concat(stuecke).toString(\"utf8\");\n if (!text) {\n return undefined;\n }\n try {\n return JSON.parse(text) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ import { AgentSession } from "./session";
2
+ import { AgentConfig, SessionInfo, SessionStore } from "./types";
3
+ /** Haelt lebende Sessions im Speicher und laedt persistierte bei Bedarf nach. */
4
+ export declare class SessionManager {
5
+ private readonly config;
6
+ private readonly store;
7
+ private readonly sessions;
8
+ constructor(config: AgentConfig, store?: SessionStore);
9
+ get(id: string): Promise<AgentSession>;
10
+ list(): Promise<SessionInfo[]>;
11
+ delete(id: string): Promise<void>;
12
+ }
@@ -0,0 +1,16 @@
1
+ import { AgentConfig, OpenAiMessage, OpenAiToolCall } from "./types";
2
+ export interface StreamCallbacks {
3
+ onText: (delta: string) => void;
4
+ onThinking: (delta: string) => void;
5
+ }
6
+ export interface StreamResult {
7
+ content: string;
8
+ toolCalls: OpenAiToolCall[];
9
+ finishReason: string | undefined;
10
+ }
11
+ /**
12
+ * Ein Streaming-Aufruf gegen /chat/completions. Denk-Anteile kommen je nach Backend als
13
+ * eigenes reasoning-Feld (OpenRouter) oder als <think>-Tags im Content (Ollama) - beides
14
+ * landet in onThinking.
15
+ */
16
+ export declare function streamChatCompletion(config: AgentConfig, messages: OpenAiMessage[], signal: AbortSignal, callbacks: StreamCallbacks): Promise<StreamResult>;
@@ -0,0 +1,27 @@
1
+ import { ChatEvent } from "../events";
2
+ import { AgentConfig, SessionState, SessionStore } from "./types";
3
+ /**
4
+ * Eine Unterhaltung: haelt Verlauf und Event-Log, streamt Antworten, fuehrt Tools aus.
5
+ * Senden waehrend eines Laufs wird zum Zwischenruf und am Runden-Ende beruecksichtigt.
6
+ * Abonnenten bekommen erst den bisherigen Verlauf, dann live.
7
+ */
8
+ export declare class AgentSession {
9
+ readonly id: string;
10
+ private readonly config;
11
+ private readonly store?;
12
+ private readonly events;
13
+ private readonly history;
14
+ private readonly listeners;
15
+ private readonly steering;
16
+ private abort?;
17
+ private _running;
18
+ constructor(id: string, config: AgentConfig, store?: SessionStore | undefined, initial?: SessionState);
19
+ get running(): boolean;
20
+ subscribe(listener: (event: ChatEvent) => void): () => void;
21
+ send(text: string): void;
22
+ stop(): void;
23
+ private run;
24
+ private runTool;
25
+ private emit;
26
+ private persist;
27
+ }
@@ -0,0 +1,18 @@
1
+ import { SessionInfo, SessionState, SessionStore } from "./types";
2
+ export declare class MemorySessionStore implements SessionStore {
3
+ private readonly states;
4
+ load(id: string): Promise<SessionState | undefined>;
5
+ save(state: SessionState): Promise<void>;
6
+ list(): Promise<SessionInfo[]>;
7
+ delete(id: string): Promise<void>;
8
+ }
9
+ /** Eine JSON-Datei je Unterhaltung; geschrieben wird atomar ueber eine Temp-Datei. */
10
+ export declare class FileSessionStore implements SessionStore {
11
+ private readonly dir;
12
+ constructor(dir: string);
13
+ load(id: string): Promise<SessionState | undefined>;
14
+ save(state: SessionState): Promise<void>;
15
+ list(): Promise<SessionInfo[]>;
16
+ delete(id: string): Promise<void>;
17
+ private pfad;
18
+ }