@absolutejs/ai 0.0.1

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.
Files changed (42) hide show
  1. package/README.md +6 -0
  2. package/dist/ai/client/index.js +682 -0
  3. package/dist/ai/client/index.js.map +15 -0
  4. package/dist/ai/index.js +3317 -0
  5. package/dist/ai/index.js.map +28 -0
  6. package/dist/ai/providers/anthropic.js +371 -0
  7. package/dist/ai/providers/anthropic.js.map +10 -0
  8. package/dist/ai/providers/gemini.js +260 -0
  9. package/dist/ai/providers/gemini.js.map +10 -0
  10. package/dist/ai/providers/ollama.js +221 -0
  11. package/dist/ai/providers/ollama.js.map +10 -0
  12. package/dist/ai/providers/openai.js +322 -0
  13. package/dist/ai/providers/openai.js.map +10 -0
  14. package/dist/ai/providers/openaiCompatible.js +360 -0
  15. package/dist/ai/providers/openaiCompatible.js.map +11 -0
  16. package/dist/ai/providers/openaiResponses.js +381 -0
  17. package/dist/ai/providers/openaiResponses.js.map +10 -0
  18. package/dist/src/ai/client/actions.d.ts +186 -0
  19. package/dist/src/ai/client/connection.d.ts +9 -0
  20. package/dist/src/ai/client/createAIStream.d.ts +12 -0
  21. package/dist/src/ai/client/index.d.ts +4 -0
  22. package/dist/src/ai/client/messageStore.d.ts +12 -0
  23. package/dist/src/ai/conversationManager.d.ts +19 -0
  24. package/dist/src/ai/htmxRenderers.d.ts +3 -0
  25. package/dist/src/ai/index.d.ts +19 -0
  26. package/dist/src/ai/memoryStore.d.ts +2 -0
  27. package/dist/src/ai/protocol.d.ts +4 -0
  28. package/dist/src/ai/providers/anthropic.d.ts +3 -0
  29. package/dist/src/ai/providers/gemini.d.ts +8 -0
  30. package/dist/src/ai/providers/ollama.d.ts +6 -0
  31. package/dist/src/ai/providers/openai.d.ts +7 -0
  32. package/dist/src/ai/providers/openaiCompatible.d.ts +30 -0
  33. package/dist/src/ai/providers/openaiResponses.d.ts +33 -0
  34. package/dist/src/ai/streamAI.d.ts +2 -0
  35. package/dist/src/ai/streamAIToSSE.d.ts +6 -0
  36. package/dist/src/constants.d.ts +60 -0
  37. package/dist/src/plugins/aiChat.d.ts +239 -0
  38. package/dist/types/ai.d.ts +4773 -0
  39. package/dist/types/anthropic.d.ts +20 -0
  40. package/dist/types/session.d.ts +16 -0
  41. package/dist/types/typeGuards.d.ts +3 -0
  42. package/package.json +59 -0
@@ -0,0 +1,3317 @@
1
+ // @bun
2
+ // src/ai/providers/openai.ts
3
+ var DEFAULT_BASE_URL = "https://api.openai.com";
4
+ var SSE_DATA_PREFIX_LENGTH = 6;
5
+ var DONE_SENTINEL = "[DONE]";
6
+ var NOT_FOUND = -1;
7
+ var isRecord = (value) => typeof value === "object" && value !== null;
8
+ var isRecordArray = (value) => Array.isArray(value) && value.length > 0 && isRecord(value[0]);
9
+ var hasArrayContent = (msg) => typeof msg.content !== "string" && Array.isArray(msg.content);
10
+ var buildToolMessages = (blocks) => {
11
+ const toolUseBlocks = blocks.filter((block) => block.type === "tool_use");
12
+ const toolResultBlocks = blocks.filter((block) => block.type === "tool_result");
13
+ const messages = [];
14
+ if (toolUseBlocks.length > 0) {
15
+ messages.push({
16
+ content: null,
17
+ role: "assistant",
18
+ tool_calls: toolUseBlocks.map((block) => ({
19
+ function: {
20
+ arguments: typeof block.input === "string" ? block.input : JSON.stringify(block.input),
21
+ name: block.name
22
+ },
23
+ id: block.id,
24
+ type: "function"
25
+ }))
26
+ });
27
+ }
28
+ for (const result of toolResultBlocks) {
29
+ messages.push({
30
+ content: typeof result.content === "string" ? result.content : "",
31
+ role: "tool",
32
+ tool_call_id: result.tool_use_id
33
+ });
34
+ }
35
+ return messages;
36
+ };
37
+ var processMessageAtIndex = (result, msg, idx) => {
38
+ if (!hasArrayContent(msg)) {
39
+ return;
40
+ }
41
+ const hasToolBlocks = msg.content.some((block) => block.type === "tool_use" || block.type === "tool_result");
42
+ if (!hasToolBlocks) {
43
+ return;
44
+ }
45
+ const toolMessages = buildToolMessages(msg.content);
46
+ result.splice(idx, 1, ...toolMessages);
47
+ };
48
+ var convertSingleMessage = (result, msg, idx) => {
49
+ if (!msg) {
50
+ return;
51
+ }
52
+ processMessageAtIndex(result, msg, idx);
53
+ };
54
+ var convertToolResultMessages = (messages, params) => {
55
+ const result = [...messages];
56
+ for (let idx = 0;idx < params.messages.length; idx++) {
57
+ convertSingleMessage(result, params.messages[idx], idx);
58
+ }
59
+ return result;
60
+ };
61
+ var mapToolDefinitions = (tools) => tools.map((tool) => ({
62
+ function: {
63
+ description: tool.description,
64
+ name: tool.name,
65
+ parameters: tool.input_schema
66
+ },
67
+ type: "function"
68
+ }));
69
+ var mapContentBlockToOpenAI = (block) => {
70
+ if (block.type === "image") {
71
+ return {
72
+ image_url: {
73
+ url: `data:${block.source.media_type};base64,${block.source.data}`
74
+ },
75
+ type: "image_url"
76
+ };
77
+ }
78
+ if (block.type === "document") {
79
+ return {
80
+ file: {
81
+ file_data: `data:${block.source.media_type};base64,${block.source.data}`,
82
+ filename: block.name ?? "document.pdf"
83
+ },
84
+ type: "file"
85
+ };
86
+ }
87
+ if (block.type === "text") {
88
+ return { text: block.content, type: "text" };
89
+ }
90
+ return null;
91
+ };
92
+ var mapOpenAIContent = (msg) => {
93
+ if (typeof msg.content === "string") {
94
+ return msg.content;
95
+ }
96
+ const hasMedia = msg.content.some((block) => block.type === "image" || block.type === "document");
97
+ if (!hasMedia) {
98
+ return null;
99
+ }
100
+ return msg.content.map(mapContentBlockToOpenAI).filter((mapped) => mapped !== null);
101
+ };
102
+ var buildRequestBody = (params) => {
103
+ const messages = convertToolResultMessages(params.messages.map((msg) => ({
104
+ content: mapOpenAIContent(msg),
105
+ role: msg.role
106
+ })), params);
107
+ const body = {
108
+ messages,
109
+ model: params.model,
110
+ stream: true,
111
+ stream_options: { include_usage: true }
112
+ };
113
+ if (params.tools && params.tools.length > 0) {
114
+ body.tools = mapToolDefinitions(params.tools);
115
+ }
116
+ return body;
117
+ };
118
+ var parseToolInput = (rawArguments) => {
119
+ try {
120
+ return JSON.parse(rawArguments);
121
+ } catch {
122
+ return rawArguments;
123
+ }
124
+ };
125
+ var flushPendingToolCalls = function* (pendingToolCalls) {
126
+ for (const [, tool] of pendingToolCalls) {
127
+ const input = parseToolInput(tool.arguments);
128
+ yield {
129
+ id: tool.id,
130
+ input,
131
+ name: tool.name,
132
+ type: "tool_use"
133
+ };
134
+ }
135
+ pendingToolCalls.clear();
136
+ };
137
+ var extractUsage = (parsedUsage) => ({
138
+ inputTokens: parsedUsage.prompt_tokens ?? 0,
139
+ outputTokens: parsedUsage.completion_tokens ?? 0
140
+ });
141
+ var resolveToolCallIndex = (toolCall) => {
142
+ const raw = typeof toolCall.index === "number" ? toolCall.index : NOT_FOUND;
143
+ return raw < 0 ? undefined : raw;
144
+ };
145
+ var initPendingToolCall = (toolCall, func, index, pendingToolCalls) => {
146
+ if (pendingToolCalls.has(index)) {
147
+ return;
148
+ }
149
+ const toolId = typeof toolCall.id === "string" ? toolCall.id : "";
150
+ const toolName = func && typeof func.name === "string" ? func.name : "";
151
+ pendingToolCalls.set(index, {
152
+ arguments: "",
153
+ id: toolId,
154
+ name: toolName
155
+ });
156
+ };
157
+ var updatePendingToolCall = (toolCall, func, pending) => {
158
+ if (typeof toolCall.id === "string") {
159
+ pending.id = toolCall.id;
160
+ }
161
+ if (func && typeof func.name === "string") {
162
+ pending.name = func.name;
163
+ }
164
+ if (func && typeof func.arguments === "string") {
165
+ pending.arguments += func.arguments;
166
+ }
167
+ };
168
+ var processToolCallDelta = (toolCall, pendingToolCalls) => {
169
+ const index = resolveToolCallIndex(toolCall);
170
+ if (index === undefined) {
171
+ return;
172
+ }
173
+ const func = isRecord(toolCall.function) ? toolCall.function : null;
174
+ initPendingToolCall(toolCall, func, index, pendingToolCalls);
175
+ const pending = pendingToolCalls.get(index);
176
+ if (!pending) {
177
+ return;
178
+ }
179
+ updatePendingToolCall(toolCall, func, pending);
180
+ };
181
+ var processToolCallDeltas = (toolCalls, pendingToolCalls) => {
182
+ for (const toolCall of toolCalls) {
183
+ processToolCallDelta(toolCall, pendingToolCalls);
184
+ }
185
+ };
186
+ var processDelta = function* (delta, pendingToolCalls) {
187
+ if (typeof delta.content === "string") {
188
+ yield { content: delta.content, type: "text" };
189
+ }
190
+ if (isRecordArray(delta.tool_calls)) {
191
+ processToolCallDeltas(delta.tool_calls, pendingToolCalls);
192
+ }
193
+ };
194
+ var processChoice = function* (choice, pendingToolCalls) {
195
+ const delta = isRecord(choice.delta) ? choice.delta : null;
196
+ if (delta) {
197
+ yield* processDelta(delta, pendingToolCalls);
198
+ }
199
+ if (choice.finish_reason === "tool_calls") {
200
+ yield* flushPendingToolCalls(pendingToolCalls);
201
+ }
202
+ };
203
+ var narrowUsageRecord = (parsed) => {
204
+ if (!isRecord(parsed.usage)) {
205
+ return;
206
+ }
207
+ const { usage } = parsed;
208
+ const promptTokens = typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : 0;
209
+ const completionTokens = typeof usage.completion_tokens === "number" ? usage.completion_tokens : 0;
210
+ return extractUsage({
211
+ completion_tokens: completionTokens,
212
+ prompt_tokens: promptTokens
213
+ });
214
+ };
215
+ var processSSELine = function* (line, pendingToolCalls, currentUsage) {
216
+ const trimmed = line.trim();
217
+ if (!trimmed || !trimmed.startsWith("data: ")) {
218
+ return;
219
+ }
220
+ const data = trimmed.slice(SSE_DATA_PREFIX_LENGTH);
221
+ if (data === DONE_SENTINEL) {
222
+ yield* flushPendingToolCalls(pendingToolCalls);
223
+ yield { type: "done", usage: currentUsage };
224
+ return;
225
+ }
226
+ let parsed;
227
+ try {
228
+ parsed = JSON.parse(data);
229
+ } catch {
230
+ return;
231
+ }
232
+ const usageUpdate = narrowUsageRecord(parsed);
233
+ if (usageUpdate) {
234
+ yield { type: "usage_update", usage: usageUpdate };
235
+ }
236
+ const { choices } = parsed;
237
+ if (!isRecordArray(choices)) {
238
+ return;
239
+ }
240
+ const [firstChoice] = choices;
241
+ if (!firstChoice) {
242
+ return;
243
+ }
244
+ yield* processChoice(firstChoice, pendingToolCalls);
245
+ };
246
+ var isUsageUpdate = (chunk) => chunk.type === "usage_update";
247
+ var collectYieldableChunks = (line, pendingToolCalls, usageRef) => {
248
+ const allChunks = Array.from(processSSELine(line, pendingToolCalls, usageRef.current));
249
+ const usageChunks = allChunks.filter(isUsageUpdate);
250
+ const lastUsage = usageChunks.at(NOT_FOUND);
251
+ if (lastUsage) {
252
+ usageRef.current = lastUsage.usage;
253
+ }
254
+ return allChunks.filter((chunk) => !isUsageUpdate(chunk));
255
+ };
256
+ var processSSELines = function* (lines, pendingToolCalls, usageRef) {
257
+ for (const line of lines) {
258
+ yield* collectYieldableChunks(line, pendingToolCalls, usageRef);
259
+ }
260
+ };
261
+ var processStreamValue = (value, decoder, state) => {
262
+ state.buffer += decoder.decode(value, { stream: true });
263
+ const lines = state.buffer.split(`
264
+ `);
265
+ state.buffer = lines.pop() ?? "";
266
+ return lines;
267
+ };
268
+ var drainReader = async function* (reader, decoder, state, signal) {
269
+ for (let result = await reader.read();!result.done && !signal?.aborted; result = await reader.read()) {
270
+ const lines = processStreamValue(result.value, decoder, state);
271
+ yield* processSSELines(lines, state.pendingToolCalls, state.usageRef);
272
+ }
273
+ };
274
+ var parseSSEStream = async function* (body, signal) {
275
+ const reader = body.getReader();
276
+ const decoder = new TextDecoder;
277
+ const state = {
278
+ buffer: "",
279
+ pendingToolCalls: new Map,
280
+ usageRef: { current: undefined }
281
+ };
282
+ try {
283
+ yield* drainReader(reader, decoder, state, signal);
284
+ yield { type: "done", usage: state.usageRef.current };
285
+ } finally {
286
+ reader.releaseLock();
287
+ }
288
+ };
289
+ var fetchOpenAIStream = async function* (baseUrl, apiKey, body, signal) {
290
+ const response = await fetch(`${baseUrl}/v1/chat/completions`, {
291
+ body: JSON.stringify(body),
292
+ headers: {
293
+ Authorization: `Bearer ${apiKey}`,
294
+ "Content-Type": "application/json"
295
+ },
296
+ method: "POST",
297
+ signal
298
+ });
299
+ if (!response.ok) {
300
+ const errorText = await response.text();
301
+ throw new Error(`OpenAI API error ${response.status}: ${errorText}`);
302
+ }
303
+ if (!response.body) {
304
+ throw new Error("OpenAI API returned no response body");
305
+ }
306
+ yield* parseSSEStream(response.body, signal);
307
+ };
308
+ var openai = (config) => {
309
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
310
+ return {
311
+ stream: (params) => {
312
+ const body = buildRequestBody(params);
313
+ return fetchOpenAIStream(baseUrl, config.apiKey, body, params.signal);
314
+ }
315
+ };
316
+ };
317
+
318
+ // src/ai/providers/openaiCompatible.ts
319
+ var alibaba = (config) => openaiCompatible({
320
+ apiKey: config.apiKey,
321
+ baseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode"
322
+ });
323
+ var deepseek = (config) => openaiCompatible({
324
+ apiKey: config.apiKey,
325
+ baseUrl: "https://api.deepseek.com"
326
+ });
327
+ var google = (config) => openaiCompatible({
328
+ apiKey: config.apiKey,
329
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai"
330
+ });
331
+ var meta = (config) => openaiCompatible({
332
+ apiKey: config.apiKey,
333
+ baseUrl: "https://api.llama.com/compat/v1"
334
+ });
335
+ var mistralai = (config) => openaiCompatible({
336
+ apiKey: config.apiKey,
337
+ baseUrl: "https://api.mistral.ai"
338
+ });
339
+ var moonshot = (config) => openaiCompatible({
340
+ apiKey: config.apiKey,
341
+ baseUrl: "https://api.moonshot.ai"
342
+ });
343
+ var openaiCompatible = (config) => openai({ apiKey: config.apiKey, baseUrl: config.baseUrl });
344
+ var xai = (config) => openaiCompatible({
345
+ apiKey: config.apiKey,
346
+ baseUrl: "https://api.x.ai"
347
+ });
348
+
349
+ // src/ai/providers/openaiResponses.ts
350
+ var DEFAULT_BASE_URL2 = "https://api.openai.com";
351
+ var EVENT_PREFIX_LENGTH = 7;
352
+ var DATA_PREFIX_LENGTH = 6;
353
+ var isRecord2 = (value) => typeof value === "object" && value !== null;
354
+ var isRecordArray2 = (value) => Array.isArray(value) && value.length > 0 && isRecord2(value[0]);
355
+ var mapBlockToResponsesFormat = (block) => {
356
+ if (block.type === "text") {
357
+ return { text: block.content, type: "input_text" };
358
+ }
359
+ if (block.type === "image") {
360
+ return {
361
+ image_url: {
362
+ url: `data:${block.source.media_type};base64,${block.source.data}`
363
+ },
364
+ type: "input_image"
365
+ };
366
+ }
367
+ if (block.type === "document") {
368
+ return {
369
+ file: {
370
+ file_data: `data:${block.source.media_type};base64,${block.source.data}`,
371
+ filename: block.name ?? "document.pdf"
372
+ },
373
+ type: "input_file"
374
+ };
375
+ }
376
+ return null;
377
+ };
378
+ var mapContentToResponsesFormat = (content) => {
379
+ if (typeof content === "string") {
380
+ return content;
381
+ }
382
+ const parts = content.map(mapBlockToResponsesFormat).filter((mapped) => mapped !== null);
383
+ return parts.length > 0 ? parts : "";
384
+ };
385
+ var hasToolBlocks = (content) => content.some((block) => block.type === "tool_use" || block.type === "tool_result");
386
+ var convertToolBlock = (block) => {
387
+ if (block.type === "tool_use") {
388
+ return {
389
+ arguments: typeof block.input === "string" ? block.input : JSON.stringify(block.input),
390
+ call_id: block.id,
391
+ name: block.name,
392
+ type: "function_call"
393
+ };
394
+ }
395
+ if (block.type === "tool_result") {
396
+ return {
397
+ call_id: block.tool_use_id,
398
+ output: typeof block.content === "string" ? block.content : "",
399
+ type: "function_call_output"
400
+ };
401
+ }
402
+ return null;
403
+ };
404
+ var convertToolBlocks = (content) => content.map(convertToolBlock).filter((converted) => converted !== null);
405
+ var convertMessage = (msg) => {
406
+ if (typeof msg.content !== "string" && Array.isArray(msg.content) && hasToolBlocks(msg.content)) {
407
+ return convertToolBlocks(msg.content);
408
+ }
409
+ const content = mapContentToResponsesFormat(msg.content);
410
+ return [
411
+ {
412
+ content,
413
+ role: msg.role === "system" ? "developer" : msg.role,
414
+ type: "message"
415
+ }
416
+ ];
417
+ };
418
+ var buildInput = (messages) => {
419
+ const input = [];
420
+ for (const msg of messages) {
421
+ input.push(...convertMessage(msg));
422
+ }
423
+ return input;
424
+ };
425
+ var mapToolDefinition = (tool) => ({
426
+ description: tool.description,
427
+ name: tool.name,
428
+ parameters: tool.input_schema,
429
+ type: "function"
430
+ });
431
+ var buildTools = (tools, isImageModel) => {
432
+ const mapped = tools ? tools.map(mapToolDefinition) : [];
433
+ const result = [...mapped];
434
+ if (isImageModel) {
435
+ result.push({ type: "image_generation" });
436
+ }
437
+ return result.length > 0 ? result : undefined;
438
+ };
439
+ var buildRequestBody2 = (params, isImageModel) => {
440
+ const body = {
441
+ input: buildInput(params.messages),
442
+ model: params.model,
443
+ stream: true
444
+ };
445
+ if (params.systemPrompt) {
446
+ body.instructions = params.systemPrompt;
447
+ }
448
+ const tools = buildTools(params.tools, isImageModel);
449
+ if (tools) {
450
+ body.tools = tools;
451
+ }
452
+ if (params.thinking) {
453
+ body.reasoning = {
454
+ effort: "high",
455
+ summary: "auto"
456
+ };
457
+ }
458
+ return body;
459
+ };
460
+ var parseJSON = (data) => {
461
+ try {
462
+ return JSON.parse(data);
463
+ } catch {
464
+ return null;
465
+ }
466
+ };
467
+ var parseToolInput2 = (rawArguments) => {
468
+ try {
469
+ return JSON.parse(rawArguments);
470
+ } catch {
471
+ return rawArguments;
472
+ }
473
+ };
474
+ var extractUsage2 = (response) => {
475
+ if (!isRecord2(response.usage)) {
476
+ return;
477
+ }
478
+ const { usage } = response;
479
+ return {
480
+ inputTokens: typeof usage.input_tokens === "number" ? usage.input_tokens : 0,
481
+ outputTokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0
482
+ };
483
+ };
484
+ var extractMimeFormat = (mimeType) => {
485
+ if (typeof mimeType !== "string") {
486
+ return "png";
487
+ }
488
+ if (mimeType.includes("jpeg"))
489
+ return "jpeg";
490
+ if (mimeType.includes("webp"))
491
+ return "webp";
492
+ return "png";
493
+ };
494
+ var processTextDelta = function* (parsed) {
495
+ if (typeof parsed.delta === "string") {
496
+ yield { content: parsed.delta, type: "text" };
497
+ }
498
+ };
499
+ var processPartialImage = function* (parsed) {
500
+ const itemId = typeof parsed.item_id === "string" ? parsed.item_id : undefined;
501
+ const b64 = typeof parsed.partial_image_b64 === "string" ? parsed.partial_image_b64 : undefined;
502
+ if (b64) {
503
+ yield {
504
+ data: b64,
505
+ format: "png",
506
+ imageId: itemId,
507
+ isPartial: true,
508
+ type: "image"
509
+ };
510
+ }
511
+ };
512
+ var processFunctionCallArgumentsDelta = (parsed, pendingCalls) => {
513
+ const itemId = typeof parsed.item_id === "string" ? parsed.item_id : "";
514
+ const callId = typeof parsed.call_id === "string" ? parsed.call_id : "";
515
+ const delta = typeof parsed.arguments_delta === "string" ? parsed.arguments_delta : "";
516
+ const existing = pendingCalls.get(itemId);
517
+ if (existing) {
518
+ existing.arguments += delta;
519
+ } else {
520
+ pendingCalls.set(itemId, {
521
+ arguments: delta,
522
+ callId,
523
+ name: ""
524
+ });
525
+ }
526
+ };
527
+ var processFunctionCallArgumentsDone = function* (parsed, pendingCalls) {
528
+ const itemId = typeof parsed.item_id === "string" ? parsed.item_id : "";
529
+ const callId = typeof parsed.call_id === "string" ? parsed.call_id : "";
530
+ const fullArgs = typeof parsed.arguments === "string" ? parsed.arguments : "";
531
+ const pending = pendingCalls.get(itemId);
532
+ const name = pending?.name ?? "";
533
+ const args = fullArgs || pending?.arguments || "";
534
+ pendingCalls.delete(itemId);
535
+ yield {
536
+ id: callId || pending?.callId || itemId,
537
+ input: parseToolInput2(args),
538
+ name,
539
+ type: "tool_use"
540
+ };
541
+ };
542
+ var processOutputItemAdded = (parsed, pendingCalls) => {
543
+ if (!isRecord2(parsed.item)) {
544
+ return;
545
+ }
546
+ const { item } = parsed;
547
+ const itemId = typeof item.id === "string" ? item.id : "";
548
+ const itemType = typeof item.type === "string" ? item.type : "";
549
+ if (itemType !== "function_call") {
550
+ return;
551
+ }
552
+ const callId = typeof item.call_id === "string" ? item.call_id : "";
553
+ const name = typeof item.name === "string" ? item.name : "";
554
+ pendingCalls.set(itemId, {
555
+ arguments: "",
556
+ callId,
557
+ name
558
+ });
559
+ };
560
+ var isCompletedImageGeneration = (item) => item.type === "image_generation_call" && item.status === "completed" && typeof item.result === "string" && item.result !== "";
561
+ var buildImageChunk = (item) => ({
562
+ data: typeof item.result === "string" ? item.result : "",
563
+ format: extractMimeFormat(item.output_format),
564
+ imageId: typeof item.id === "string" ? item.id : undefined,
565
+ isPartial: false,
566
+ revisedPrompt: typeof item.revised_prompt === "string" ? item.revised_prompt : undefined,
567
+ type: "image"
568
+ });
569
+ var extractImageFromOutput = function* (output) {
570
+ const completedImages = output.filter(isCompletedImageGeneration);
571
+ for (const item of completedImages) {
572
+ yield buildImageChunk(item);
573
+ }
574
+ };
575
+ var processCompleted = function* (parsed) {
576
+ if (!isRecord2(parsed.response)) {
577
+ yield { type: "done", usage: undefined };
578
+ return;
579
+ }
580
+ const { response } = parsed;
581
+ const usage = extractUsage2(response);
582
+ if (isRecordArray2(response.output)) {
583
+ yield* extractImageFromOutput(response.output);
584
+ }
585
+ yield { type: "done", usage };
586
+ };
587
+ var processSSEEvent = function* (eventType, parsed, pendingCalls) {
588
+ switch (eventType) {
589
+ case "response.reasoning_summary_text.delta": {
590
+ const delta = typeof parsed.delta === "string" ? parsed.delta : "";
591
+ if (!delta)
592
+ break;
593
+ yield {
594
+ content: delta,
595
+ type: "thinking"
596
+ };
597
+ break;
598
+ }
599
+ case "response.output_text.delta":
600
+ yield* processTextDelta(parsed);
601
+ break;
602
+ case "response.image_generation_call.partial_image":
603
+ yield* processPartialImage(parsed);
604
+ break;
605
+ case "response.output_item.added":
606
+ processOutputItemAdded(parsed, pendingCalls);
607
+ break;
608
+ case "response.function_call_arguments.delta":
609
+ processFunctionCallArgumentsDelta(parsed, pendingCalls);
610
+ break;
611
+ case "response.function_call_arguments.done":
612
+ yield* processFunctionCallArgumentsDone(parsed, pendingCalls);
613
+ break;
614
+ case "response.completed":
615
+ yield* processCompleted(parsed);
616
+ break;
617
+ case "response.failed":
618
+ case "response.incomplete": {
619
+ const respObj = isRecord2(parsed.response) ? parsed.response : parsed;
620
+ const errMsg = isRecord2(respObj.error) && typeof respObj.error.message === "string" ? respObj.error.message : `OpenAI Responses API: ${eventType}`;
621
+ throw new Error(errMsg);
622
+ }
623
+ }
624
+ };
625
+ var flushSSEBuffer = function* (state) {
626
+ if (!state.currentEvent || !state.buffer) {
627
+ return;
628
+ }
629
+ const parsed = parseJSON(state.buffer);
630
+ if (parsed) {
631
+ yield* processSSEEvent(state.currentEvent, parsed, state.pendingCalls);
632
+ }
633
+ state.currentEvent = "";
634
+ state.buffer = "";
635
+ };
636
+ var parseSSELine = (trimmed, state) => {
637
+ if (trimmed.startsWith("event: ")) {
638
+ state.currentEvent = trimmed.slice(EVENT_PREFIX_LENGTH);
639
+ } else if (trimmed.startsWith("data: ")) {
640
+ state.buffer = trimmed.slice(DATA_PREFIX_LENGTH);
641
+ }
642
+ };
643
+ var processSSELine2 = function* (line, state) {
644
+ const trimmed = line.trim();
645
+ if (trimmed) {
646
+ parseSSELine(trimmed, state);
647
+ return;
648
+ }
649
+ yield* flushSSEBuffer(state);
650
+ };
651
+ var processSSELines2 = function* (lines, state) {
652
+ for (const line of lines) {
653
+ yield* processSSELine2(line, state);
654
+ }
655
+ };
656
+ var drainReader2 = async function* (reader, decoder, state, signal) {
657
+ let textBuffer = "";
658
+ for (let result = await reader.read();!result.done && !signal?.aborted; result = await reader.read()) {
659
+ textBuffer += decoder.decode(result.value, { stream: true });
660
+ const lines = textBuffer.split(`
661
+ `);
662
+ textBuffer = lines.pop() ?? "";
663
+ yield* processSSELines2(lines, state);
664
+ }
665
+ if (textBuffer.trim()) {
666
+ yield* processSSELines2([textBuffer, ""], state);
667
+ }
668
+ };
669
+ var parseSSEStream2 = async function* (body, signal) {
670
+ const reader = body.getReader();
671
+ const decoder = new TextDecoder;
672
+ const state = {
673
+ buffer: "",
674
+ currentEvent: "",
675
+ pendingCalls: new Map,
676
+ usage: undefined
677
+ };
678
+ try {
679
+ yield* drainReader2(reader, decoder, state, signal);
680
+ } finally {
681
+ reader.releaseLock();
682
+ }
683
+ };
684
+ var fetchResponsesStream = async function* (baseUrl, apiKey, body, signal) {
685
+ const response = await fetch(`${baseUrl}/v1/responses`, {
686
+ body: JSON.stringify(body),
687
+ headers: {
688
+ Authorization: `Bearer ${apiKey}`,
689
+ "Content-Type": "application/json"
690
+ },
691
+ method: "POST",
692
+ signal
693
+ });
694
+ if (!response.ok) {
695
+ const errorText = await response.text();
696
+ throw new Error(`OpenAI Responses API error ${response.status}: ${errorText}`);
697
+ }
698
+ if (!response.body) {
699
+ throw new Error("OpenAI Responses API returned no response body");
700
+ }
701
+ yield* parseSSEStream2(response.body, signal);
702
+ };
703
+ var resolveImageModels = (imageModels) => {
704
+ if (!imageModels) {
705
+ return new Set;
706
+ }
707
+ if (imageModels instanceof Set) {
708
+ return imageModels;
709
+ }
710
+ return new Set(imageModels);
711
+ };
712
+ var openaiResponses = (config) => {
713
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL2;
714
+ const imageModels = resolveImageModels(config.imageModels);
715
+ return {
716
+ stream: (params) => {
717
+ const isImageModel = imageModels.has(params.model);
718
+ const body = buildRequestBody2(params, isImageModel);
719
+ return fetchResponsesStream(baseUrl, config.apiKey, body, params.signal);
720
+ }
721
+ };
722
+ };
723
+
724
+ // src/ai/providers/gemini.ts
725
+ var DEFAULT_BASE_URL3 = "https://generativelanguage.googleapis.com";
726
+ var SSE_DATA_PREFIX_LENGTH2 = 6;
727
+ var isRecord3 = (value) => typeof value === "object" && value !== null;
728
+ var isRecordArray3 = (value) => Array.isArray(value) && value.length > 0 && isRecord3(value[0]);
729
+ var mapRole = (role) => {
730
+ if (role === "assistant" || role === "system")
731
+ return "model";
732
+ return "user";
733
+ };
734
+ var mapContentBlock = (block) => {
735
+ switch (block.type) {
736
+ case "text":
737
+ return { text: block.content };
738
+ case "image":
739
+ return {
740
+ inlineData: {
741
+ data: block.source.data,
742
+ mimeType: block.source.media_type
743
+ }
744
+ };
745
+ case "document":
746
+ return {
747
+ inlineData: {
748
+ data: block.source.data,
749
+ mimeType: block.source.media_type
750
+ }
751
+ };
752
+ case "tool_use":
753
+ return {
754
+ functionCall: {
755
+ args: typeof block.input === "string" ? JSON.parse(block.input) : block.input,
756
+ name: block.name
757
+ }
758
+ };
759
+ case "tool_result":
760
+ return {
761
+ functionResponse: {
762
+ name: block.tool_use_id,
763
+ response: { result: block.content }
764
+ }
765
+ };
766
+ default:
767
+ return null;
768
+ }
769
+ };
770
+ var convertMessageContent = (content) => {
771
+ if (typeof content === "string") {
772
+ return [{ text: content }];
773
+ }
774
+ return content.map(mapContentBlock).filter((mapped) => mapped !== null);
775
+ };
776
+ var hasFunctionResponse = (content) => content.some((block) => block.type === "tool_result");
777
+ var convertSingleMessage2 = (msg) => {
778
+ if (typeof msg.content !== "string" && hasFunctionResponse(msg.content)) {
779
+ return { parts: convertMessageContent(msg.content), role: "user" };
780
+ }
781
+ return {
782
+ parts: convertMessageContent(msg.content),
783
+ role: mapRole(msg.role)
784
+ };
785
+ };
786
+ var convertMessages = (messages) => messages.map(convertSingleMessage2);
787
+ var mapToolDefinitions2 = (tools) => tools.map((tool) => ({
788
+ description: tool.description,
789
+ name: tool.name,
790
+ parameters: tool.input_schema
791
+ }));
792
+ var buildRequestBody3 = (params, isImageModel) => {
793
+ const body = {
794
+ contents: convertMessages(params.messages)
795
+ };
796
+ if (isImageModel) {
797
+ body.generationConfig = {
798
+ responseModalities: ["TEXT", "IMAGE"]
799
+ };
800
+ }
801
+ if (params.systemPrompt) {
802
+ body.systemInstruction = {
803
+ parts: [{ text: params.systemPrompt }]
804
+ };
805
+ }
806
+ if (params.tools && params.tools.length > 0) {
807
+ body.tools = [
808
+ { functionDeclarations: mapToolDefinitions2(params.tools) }
809
+ ];
810
+ }
811
+ return body;
812
+ };
813
+ var extractMimeFormat2 = (mimeType) => {
814
+ if (typeof mimeType !== "string") {
815
+ return "png";
816
+ }
817
+ if (mimeType.includes("jpeg") || mimeType.includes("jpg"))
818
+ return "jpeg";
819
+ if (mimeType.includes("webp"))
820
+ return "webp";
821
+ return "png";
822
+ };
823
+ var processTextPart = function* (part) {
824
+ if (typeof part.text === "string" && part.text) {
825
+ yield { content: part.text, type: "text" };
826
+ }
827
+ };
828
+ var processInlineDataPart = function* (inlineData) {
829
+ const data = typeof inlineData.data === "string" ? inlineData.data : "";
830
+ const { mimeType } = inlineData;
831
+ if (!data) {
832
+ return;
833
+ }
834
+ yield {
835
+ data,
836
+ format: extractMimeFormat2(mimeType),
837
+ isPartial: false,
838
+ type: "image"
839
+ };
840
+ };
841
+ var processFunctionCallPart = function* (functionCall) {
842
+ const name = typeof functionCall.name === "string" ? functionCall.name : "";
843
+ const args = functionCall.args ?? {};
844
+ yield {
845
+ id: `gemini-${name}-${Date.now()}`,
846
+ input: args,
847
+ name,
848
+ type: "tool_use"
849
+ };
850
+ };
851
+ var processPart = function* (part) {
852
+ if ("text" in part) {
853
+ yield* processTextPart(part);
854
+ }
855
+ if (isRecord3(part.inlineData)) {
856
+ yield* processInlineDataPart(part.inlineData);
857
+ }
858
+ if (isRecord3(part.functionCall)) {
859
+ yield* processFunctionCallPart(part.functionCall);
860
+ }
861
+ };
862
+ var extractUsage3 = (parsed) => {
863
+ if (!isRecord3(parsed.usageMetadata)) {
864
+ return;
865
+ }
866
+ const { usageMetadata } = parsed;
867
+ return {
868
+ inputTokens: typeof usageMetadata.promptTokenCount === "number" ? usageMetadata.promptTokenCount : 0,
869
+ outputTokens: typeof usageMetadata.candidatesTokenCount === "number" ? usageMetadata.candidatesTokenCount : 0
870
+ };
871
+ };
872
+ var processChunk = function* (parsed, state) {
873
+ const usage = extractUsage3(parsed);
874
+ if (usage) {
875
+ state.usage = usage;
876
+ }
877
+ if (!isRecordArray3(parsed.candidates)) {
878
+ return;
879
+ }
880
+ const [candidate] = parsed.candidates;
881
+ if (!candidate || !isRecord3(candidate.content)) {
882
+ return;
883
+ }
884
+ const { content } = candidate;
885
+ if (!isRecordArray3(content.parts)) {
886
+ return;
887
+ }
888
+ for (const part of content.parts) {
889
+ yield* processPart(part);
890
+ }
891
+ };
892
+ var processSSELine3 = function* (line, state) {
893
+ const trimmed = line.trim();
894
+ if (!trimmed || !trimmed.startsWith("data: ")) {
895
+ return;
896
+ }
897
+ const data = trimmed.slice(SSE_DATA_PREFIX_LENGTH2);
898
+ let parsed;
899
+ try {
900
+ parsed = JSON.parse(data);
901
+ } catch {
902
+ return;
903
+ }
904
+ yield* processChunk(parsed, state);
905
+ };
906
+ var processSSEBuffer = function* (lines, state) {
907
+ for (const line of lines) {
908
+ yield* processSSELine3(line, state);
909
+ }
910
+ };
911
+ var drainReader3 = async function* (reader, decoder, state, signal) {
912
+ let textBuffer = "";
913
+ for (let result = await reader.read();!result.done && !signal?.aborted; result = await reader.read()) {
914
+ textBuffer += decoder.decode(result.value, { stream: true });
915
+ const lines = textBuffer.split(`
916
+ `);
917
+ textBuffer = lines.pop() ?? "";
918
+ yield* processSSEBuffer(lines, state);
919
+ }
920
+ if (textBuffer.trim()) {
921
+ yield* processSSELine3(textBuffer, state);
922
+ }
923
+ yield { type: "done", usage: state.usage };
924
+ };
925
+ var parseSSEStream3 = async function* (body, signal) {
926
+ const reader = body.getReader();
927
+ const decoder = new TextDecoder;
928
+ const state = {
929
+ buffer: "",
930
+ usage: undefined
931
+ };
932
+ try {
933
+ yield* drainReader3(reader, decoder, state, signal);
934
+ } finally {
935
+ reader.releaseLock();
936
+ }
937
+ };
938
+ var fetchGeminiStream = async function* (baseUrl, apiKey, model, body, signal) {
939
+ const url = `${baseUrl}/v1beta/models/${model}:streamGenerateContent?alt=sse&key=${apiKey}`;
940
+ const response = await fetch(url, {
941
+ body: JSON.stringify(body),
942
+ headers: {
943
+ "Content-Type": "application/json"
944
+ },
945
+ method: "POST",
946
+ signal
947
+ });
948
+ if (!response.ok) {
949
+ const errorText = await response.text();
950
+ throw new Error(`Gemini API error ${response.status}: ${errorText}`);
951
+ }
952
+ if (!response.body) {
953
+ throw new Error("Gemini API returned no response body");
954
+ }
955
+ yield* parseSSEStream3(response.body, signal);
956
+ };
957
+ var resolveImageModels2 = (raw) => {
958
+ if (!raw) {
959
+ return new Set;
960
+ }
961
+ if (raw instanceof Set) {
962
+ return raw;
963
+ }
964
+ return new Set(raw);
965
+ };
966
+ var gemini = (config) => {
967
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL3;
968
+ const imageModels = resolveImageModels2(config.imageModels);
969
+ return {
970
+ stream: (params) => {
971
+ const isImageModel = imageModels.has(params.model);
972
+ const body = buildRequestBody3(params, isImageModel);
973
+ return fetchGeminiStream(baseUrl, config.apiKey, params.model, body, params.signal);
974
+ }
975
+ };
976
+ };
977
+
978
+ // src/ai/providers/anthropic.ts
979
+ var DEFAULT_BASE_URL4 = "https://api.anthropic.com";
980
+ var API_VERSION = "2023-06-01";
981
+ var MAX_TOKENS = 8192;
982
+ var EVENT_PREFIX_LENGTH2 = 7;
983
+ var DATA_PREFIX_LENGTH2 = 6;
984
+ var EMPTY_CHUNKS = [];
985
+ var isRecord4 = (val) => typeof val === "object" && val !== null;
986
+ var mapContentBlock2 = (block) => {
987
+ if (block.type === "thinking") {
988
+ return {
989
+ signature: block.signature,
990
+ thinking: block.thinking,
991
+ type: "thinking"
992
+ };
993
+ }
994
+ if (block.type === "image") {
995
+ return {
996
+ source: block.source,
997
+ type: "image"
998
+ };
999
+ }
1000
+ if (block.type === "document") {
1001
+ return {
1002
+ source: block.source,
1003
+ type: "document"
1004
+ };
1005
+ }
1006
+ if (block.type === "tool_result") {
1007
+ return {
1008
+ content: block.content,
1009
+ tool_use_id: block.tool_use_id,
1010
+ type: "tool_result"
1011
+ };
1012
+ }
1013
+ if (block.type === "tool_use") {
1014
+ return {
1015
+ id: block.id,
1016
+ input: block.input,
1017
+ name: block.name,
1018
+ type: "tool_use"
1019
+ };
1020
+ }
1021
+ return { text: block.content, type: "text" };
1022
+ };
1023
+ var mapMessage = (msg) => ({
1024
+ content: typeof msg.content === "string" ? msg.content : msg.content.map(mapContentBlock2),
1025
+ role: msg.role === "system" ? "user" : msg.role
1026
+ });
1027
+ var mapToolDefinition2 = (tool) => ({
1028
+ description: tool.description,
1029
+ input_schema: tool.input_schema,
1030
+ name: tool.name
1031
+ });
1032
+ var buildRequestBody4 = (params) => {
1033
+ const messages = params.messages.filter((msg) => msg.role !== "system").map(mapMessage);
1034
+ const body = {
1035
+ max_tokens: MAX_TOKENS,
1036
+ messages,
1037
+ model: params.model,
1038
+ stream: true
1039
+ };
1040
+ if (params.systemPrompt) {
1041
+ body.system = params.systemPrompt;
1042
+ }
1043
+ if (params.tools && params.tools.length > 0) {
1044
+ body.tools = params.tools.map(mapToolDefinition2);
1045
+ }
1046
+ if (params.thinking) {
1047
+ body.thinking = params.thinking;
1048
+ body.max_tokens = Math.max(MAX_TOKENS, params.thinking.budget_tokens + MAX_TOKENS);
1049
+ }
1050
+ return body;
1051
+ };
1052
+ var classifyLine = (line) => {
1053
+ if (line.startsWith("event: ")) {
1054
+ return {
1055
+ field: "event",
1056
+ value: line.slice(EVENT_PREFIX_LENGTH2)
1057
+ };
1058
+ }
1059
+ if (line.startsWith("data: ")) {
1060
+ return {
1061
+ field: "data",
1062
+ value: line.slice(DATA_PREFIX_LENGTH2)
1063
+ };
1064
+ }
1065
+ return;
1066
+ };
1067
+ var applyClassified = (acc, classified) => {
1068
+ if (!classified) {
1069
+ return acc;
1070
+ }
1071
+ if (classified.field === "event") {
1072
+ return { eventData: acc.eventData, eventType: classified.value };
1073
+ }
1074
+ return { eventData: classified.value, eventType: acc.eventType };
1075
+ };
1076
+ var parseEventLines = (event) => event.split(`
1077
+ `).reduce((acc, line) => applyClassified(acc, classifyLine(line)), {
1078
+ eventData: "",
1079
+ eventType: ""
1080
+ });
1081
+ var safeParse = (text) => {
1082
+ try {
1083
+ const result = JSON.parse(text);
1084
+ return result;
1085
+ } catch {
1086
+ return;
1087
+ }
1088
+ };
1089
+ var tryParseJson = (text) => {
1090
+ const result = safeParse(text);
1091
+ if (isRecord4(result)) {
1092
+ return result;
1093
+ }
1094
+ return;
1095
+ };
1096
+ var getRecord = (obj, key) => {
1097
+ const val = obj[key];
1098
+ if (isRecord4(val)) {
1099
+ return val;
1100
+ }
1101
+ return;
1102
+ };
1103
+ var getString = (obj, key) => {
1104
+ const val = obj[key];
1105
+ if (typeof val === "string") {
1106
+ return val;
1107
+ }
1108
+ return "";
1109
+ };
1110
+ var getNumber = (obj, key) => {
1111
+ const val = obj[key];
1112
+ if (typeof val === "number") {
1113
+ return val;
1114
+ }
1115
+ return 0;
1116
+ };
1117
+ var handleContentBlockStart = (parsed, state) => {
1118
+ const block = getRecord(parsed, "content_block");
1119
+ if (block && block.type === "tool_use") {
1120
+ state.currentToolId = getString(block, "id");
1121
+ state.currentToolName = getString(block, "name");
1122
+ state.toolInputJson = "";
1123
+ state.isThinkingBlock = false;
1124
+ } else if (block && block.type === "thinking") {
1125
+ state.isThinkingBlock = true;
1126
+ state.thinkingSignature = "";
1127
+ } else {
1128
+ state.isThinkingBlock = false;
1129
+ }
1130
+ };
1131
+ var handleContentBlockDelta = (parsed, state) => {
1132
+ const delta = getRecord(parsed, "delta");
1133
+ if (!delta) {
1134
+ return;
1135
+ }
1136
+ if (delta.type === "thinking_delta") {
1137
+ return {
1138
+ content: getString(delta, "thinking"),
1139
+ type: "thinking"
1140
+ };
1141
+ }
1142
+ if (delta.type === "text_delta") {
1143
+ return {
1144
+ content: getString(delta, "text"),
1145
+ type: "text"
1146
+ };
1147
+ }
1148
+ if (delta.type === "input_json_delta") {
1149
+ state.toolInputJson += getString(delta, "partial_json");
1150
+ }
1151
+ if (delta.type === "signature_delta") {
1152
+ state.thinkingSignature += getString(delta, "signature");
1153
+ }
1154
+ return;
1155
+ };
1156
+ var handleContentBlockStop = (state) => {
1157
+ if (state.isThinkingBlock && state.thinkingSignature) {
1158
+ state.isThinkingBlock = false;
1159
+ const signature = state.thinkingSignature;
1160
+ state.thinkingSignature = "";
1161
+ return {
1162
+ content: "",
1163
+ signature,
1164
+ type: "thinking"
1165
+ };
1166
+ }
1167
+ if (!state.currentToolId) {
1168
+ return;
1169
+ }
1170
+ const input = tryParseJson(state.toolInputJson) ?? state.toolInputJson;
1171
+ const chunk = {
1172
+ id: state.currentToolId,
1173
+ input,
1174
+ name: state.currentToolName,
1175
+ type: "tool_use"
1176
+ };
1177
+ state.currentToolId = "";
1178
+ state.currentToolName = "";
1179
+ state.toolInputJson = "";
1180
+ return chunk;
1181
+ };
1182
+ var extractUsage4 = (usageRecord, existingUsage) => {
1183
+ if (!usageRecord) {
1184
+ return existingUsage;
1185
+ }
1186
+ return {
1187
+ inputTokens: getNumber(usageRecord, "input_tokens") || existingUsage?.inputTokens || 0,
1188
+ outputTokens: getNumber(usageRecord, "output_tokens") || existingUsage?.outputTokens || 0
1189
+ };
1190
+ };
1191
+ var handleMessageDelta = (parsed, state) => {
1192
+ const deltaUsage = getRecord(parsed, "usage");
1193
+ state.usage = extractUsage4(deltaUsage, state.usage);
1194
+ };
1195
+ var handleMessageStart = (parsed, state) => {
1196
+ const message = getRecord(parsed, "message");
1197
+ if (!message) {
1198
+ return;
1199
+ }
1200
+ const startUsage = getRecord(message, "usage");
1201
+ state.usage = extractUsage4(startUsage, state.usage);
1202
+ };
1203
+ var handleError = (parsed) => {
1204
+ const error = getRecord(parsed, "error");
1205
+ const errorMessage = error ? getString(error, "message") : "";
1206
+ throw new Error(errorMessage || "Anthropic API error");
1207
+ };
1208
+ var processEvent = (eventType, parsed, state) => {
1209
+ switch (eventType) {
1210
+ case "content_block_start": {
1211
+ handleContentBlockStart(parsed, state);
1212
+ return;
1213
+ }
1214
+ case "content_block_delta": {
1215
+ return handleContentBlockDelta(parsed, state);
1216
+ }
1217
+ case "content_block_stop": {
1218
+ return handleContentBlockStop(state);
1219
+ }
1220
+ case "message_delta": {
1221
+ handleMessageDelta(parsed, state);
1222
+ return;
1223
+ }
1224
+ case "message_start": {
1225
+ handleMessageStart(parsed, state);
1226
+ return;
1227
+ }
1228
+ case "message_stop": {
1229
+ return { type: "done", usage: state.usage };
1230
+ }
1231
+ case "error": {
1232
+ handleError(parsed);
1233
+ return;
1234
+ }
1235
+ default: {
1236
+ return;
1237
+ }
1238
+ }
1239
+ };
1240
+ var processSingleEvent = (event, state) => {
1241
+ if (!event.trim()) {
1242
+ return;
1243
+ }
1244
+ const { eventData, eventType } = parseEventLines(event);
1245
+ if (!eventData) {
1246
+ return;
1247
+ }
1248
+ const parsed = tryParseJson(eventData);
1249
+ if (!parsed) {
1250
+ return;
1251
+ }
1252
+ return processEvent(eventType, parsed, state);
1253
+ };
1254
+ var collectChunk = (event, state) => {
1255
+ const chunk = processSingleEvent(event, state);
1256
+ return chunk ? [chunk] : [];
1257
+ };
1258
+ var processBufferedEvents = (eventsText, state) => {
1259
+ const events = eventsText.split(`
1260
+
1261
+ `);
1262
+ state.buffer = events.pop() ?? "";
1263
+ return events.flatMap((event) => collectChunk(event, state));
1264
+ };
1265
+ var readNextChunks = async (reader, decoder, state, signal) => {
1266
+ if (signal?.aborted) {
1267
+ return { chunks: EMPTY_CHUNKS, done: true };
1268
+ }
1269
+ const { done, value } = await reader.read();
1270
+ if (done) {
1271
+ return { chunks: EMPTY_CHUNKS, done: true };
1272
+ }
1273
+ const rawText = state.buffer + decoder.decode(value, { stream: true });
1274
+ const chunks = processBufferedEvents(rawText, state);
1275
+ return { chunks, done: false };
1276
+ };
1277
+ var findDoneChunk = (chunks) => chunks.findIndex((c) => c.type === "done");
1278
+ var sseStreamLoop = async (reader, decoder, state, signal) => {
1279
+ const result = await readNextChunks(reader, decoder, state, signal);
1280
+ if (result.done) {
1281
+ return { chunks: result.chunks, finished: true };
1282
+ }
1283
+ const doneIdx = findDoneChunk(result.chunks);
1284
+ if (doneIdx >= 0) {
1285
+ return { chunks: result.chunks.slice(0, doneIdx + 1), finished: true };
1286
+ }
1287
+ return { chunks: result.chunks, finished: false };
1288
+ };
1289
+ async function* streamChunks(reader, decoder, state, signal) {
1290
+ let finished = false;
1291
+ while (!finished) {
1292
+ const result = await sseStreamLoop(reader, decoder, state, signal);
1293
+ ({ finished } = result);
1294
+ yield* result.chunks;
1295
+ }
1296
+ }
1297
+ async function* parseSSEStream4(body, signal) {
1298
+ const reader = body.getReader();
1299
+ const decoder = new TextDecoder;
1300
+ const state = {
1301
+ buffer: "",
1302
+ currentToolId: "",
1303
+ currentToolName: "",
1304
+ isThinkingBlock: false,
1305
+ thinkingSignature: "",
1306
+ toolInputJson: "",
1307
+ usage: undefined
1308
+ };
1309
+ try {
1310
+ yield* streamChunks(reader, decoder, state, signal);
1311
+ } finally {
1312
+ reader.releaseLock();
1313
+ }
1314
+ }
1315
+ var fetchAndStream = async function* (baseUrl, config, params) {
1316
+ const body = buildRequestBody4(params);
1317
+ const response = await fetch(`${baseUrl}/v1/messages`, {
1318
+ body: JSON.stringify(body),
1319
+ headers: {
1320
+ "anthropic-version": API_VERSION,
1321
+ "Content-Type": "application/json",
1322
+ "x-api-key": config.apiKey
1323
+ },
1324
+ method: "POST",
1325
+ signal: params.signal
1326
+ });
1327
+ if (!response.ok) {
1328
+ const errorText = await response.text();
1329
+ throw new Error(`Anthropic API error ${response.status}: ${errorText}`);
1330
+ }
1331
+ if (!response.body) {
1332
+ throw new Error("Anthropic API returned no response body");
1333
+ }
1334
+ yield* parseSSEStream4(response.body, params.signal);
1335
+ };
1336
+ var anthropic = (config) => {
1337
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL4;
1338
+ return {
1339
+ stream: (params) => fetchAndStream(baseUrl, config, params)
1340
+ };
1341
+ };
1342
+
1343
+ // src/ai/providers/ollama.ts
1344
+ var DEFAULT_BASE_URL5 = "http://localhost:11434";
1345
+ var ZERO_TOKENS = 0;
1346
+ var DONE_CHUNK = { type: "done" };
1347
+ var mapToolDefinitions3 = (tools) => tools.map((tool) => ({
1348
+ function: {
1349
+ description: tool.description,
1350
+ name: tool.name,
1351
+ parameters: tool.input_schema
1352
+ },
1353
+ type: "function"
1354
+ }));
1355
+ var convertMessage2 = (msg) => {
1356
+ if (typeof msg.content === "string") {
1357
+ return [{ content: msg.content, role: msg.role }];
1358
+ }
1359
+ const results = [];
1360
+ const toolUseBlocks = msg.content.filter((block) => block.type === "tool_use");
1361
+ const toolResultBlocks = msg.content.filter((block) => block.type === "tool_result");
1362
+ if (toolUseBlocks.length > 0) {
1363
+ results.push({
1364
+ content: "",
1365
+ role: "assistant",
1366
+ tool_calls: toolUseBlocks.map((block) => ({
1367
+ function: {
1368
+ arguments: typeof block.input === "object" && block.input !== null ? { ...block.input } : {},
1369
+ name: block.name
1370
+ }
1371
+ }))
1372
+ });
1373
+ }
1374
+ for (const block of toolResultBlocks) {
1375
+ results.push({
1376
+ content: typeof block.content === "string" ? block.content : "",
1377
+ role: "tool"
1378
+ });
1379
+ }
1380
+ return results;
1381
+ };
1382
+ var buildRequestBody5 = (params) => {
1383
+ const messages = params.messages.flatMap(convertMessage2);
1384
+ if (params.systemPrompt) {
1385
+ messages.unshift({ content: params.systemPrompt, role: "system" });
1386
+ }
1387
+ const body = {
1388
+ messages,
1389
+ model: params.model,
1390
+ stream: true
1391
+ };
1392
+ if (params.tools && params.tools.length > 0) {
1393
+ body.tools = mapToolDefinitions3(params.tools);
1394
+ }
1395
+ return body;
1396
+ };
1397
+ var isRecord5 = (val) => val !== null && typeof val === "object" && !Array.isArray(val);
1398
+ var tryParseJSON = (text) => {
1399
+ try {
1400
+ const result = JSON.parse(text);
1401
+ if (isRecord5(result)) {
1402
+ return result;
1403
+ }
1404
+ return null;
1405
+ } catch {
1406
+ return null;
1407
+ }
1408
+ };
1409
+ var buildDoneChunk = (parsed) => {
1410
+ const promptEvalCount = typeof parsed.prompt_eval_count === "number" ? parsed.prompt_eval_count : undefined;
1411
+ const evalCount = typeof parsed.eval_count === "number" ? parsed.eval_count : undefined;
1412
+ const totalDuration = typeof parsed.total_duration === "number" ? parsed.total_duration : undefined;
1413
+ const hasTokenCounts = promptEvalCount !== undefined || evalCount !== undefined;
1414
+ if (hasTokenCounts) {
1415
+ return {
1416
+ type: "done",
1417
+ usage: {
1418
+ inputTokens: promptEvalCount ?? ZERO_TOKENS,
1419
+ outputTokens: evalCount ?? ZERO_TOKENS
1420
+ }
1421
+ };
1422
+ }
1423
+ if (totalDuration !== undefined) {
1424
+ return {
1425
+ type: "done",
1426
+ usage: { inputTokens: ZERO_TOKENS, outputTokens: ZERO_TOKENS }
1427
+ };
1428
+ }
1429
+ return { type: "done" };
1430
+ };
1431
+ var buildTextChunk = (content) => ({
1432
+ content,
1433
+ type: "text"
1434
+ });
1435
+ var extractToolCalls = (message) => {
1436
+ const toolCalls = message.tool_calls;
1437
+ if (!Array.isArray(toolCalls) || toolCalls.length === 0) {
1438
+ return [];
1439
+ }
1440
+ return toolCalls.filter(isRecord5).filter((call) => isRecord5(call.function)).map((call) => {
1441
+ const func = call.function;
1442
+ if (!isRecord5(func)) {
1443
+ return {
1444
+ id: crypto.randomUUID(),
1445
+ input: {},
1446
+ name: "",
1447
+ type: "tool_use"
1448
+ };
1449
+ }
1450
+ return {
1451
+ id: crypto.randomUUID(),
1452
+ input: func.arguments ?? {},
1453
+ name: typeof func.name === "string" ? func.name : "",
1454
+ type: "tool_use"
1455
+ };
1456
+ });
1457
+ };
1458
+ var extractChunks = (parsed) => {
1459
+ const { message } = parsed;
1460
+ if (!isRecord5(message)) {
1461
+ return [];
1462
+ }
1463
+ const toolChunks = extractToolCalls(message);
1464
+ if (toolChunks.length > 0) {
1465
+ return toolChunks;
1466
+ }
1467
+ const { content } = message;
1468
+ if (typeof content !== "string" || !content) {
1469
+ return [];
1470
+ }
1471
+ return [buildTextChunk(content)];
1472
+ };
1473
+ var processLine = (line) => {
1474
+ const trimmed = line.trim();
1475
+ if (!trimmed) {
1476
+ return [];
1477
+ }
1478
+ const parsed = tryParseJSON(trimmed);
1479
+ if (!parsed) {
1480
+ return [];
1481
+ }
1482
+ if (parsed.done === true) {
1483
+ return [buildDoneChunk(parsed)];
1484
+ }
1485
+ return extractChunks(parsed);
1486
+ };
1487
+ var processBufferedLines = (lines) => lines.flatMap(processLine);
1488
+ var readStreamChunks = async (reader, decoder, buffer, signal) => {
1489
+ const emptyChunks = [];
1490
+ if (signal?.aborted) {
1491
+ return {
1492
+ allChunks: emptyChunks,
1493
+ currentBuffer: buffer,
1494
+ finished: true
1495
+ };
1496
+ }
1497
+ const result = await reader.read();
1498
+ const { done, value } = result;
1499
+ if (done) {
1500
+ return {
1501
+ allChunks: emptyChunks,
1502
+ currentBuffer: buffer,
1503
+ finished: true
1504
+ };
1505
+ }
1506
+ const currentBuffer = buffer + decoder.decode(value, { stream: true });
1507
+ const lines = currentBuffer.split(`
1508
+ `);
1509
+ const remainder = lines.pop() ?? "";
1510
+ const allChunks = processBufferedLines(lines);
1511
+ const finished = allChunks.some((c) => c.type === "done");
1512
+ return { allChunks, currentBuffer: remainder, finished };
1513
+ };
1514
+ var parseNDJSONStream = async function* (body, signal) {
1515
+ const reader = body.getReader();
1516
+ try {
1517
+ yield* parseNDJSONStreamInner(reader, signal);
1518
+ } finally {
1519
+ reader.releaseLock();
1520
+ }
1521
+ };
1522
+ var parseNDJSONStreamInner = async function* (reader, signal) {
1523
+ const decoder = new TextDecoder;
1524
+ let buffer = "";
1525
+ let done = false;
1526
+ while (!done) {
1527
+ const result = await readStreamChunks(reader, decoder, buffer, signal);
1528
+ buffer = result.currentBuffer;
1529
+ done = result.finished;
1530
+ yield* result.allChunks;
1531
+ }
1532
+ yield DONE_CHUNK;
1533
+ };
1534
+ var fetchAndStream2 = async function* (baseUrl, params) {
1535
+ const requestBody = buildRequestBody5(params);
1536
+ const response = await fetch(`${baseUrl}/api/chat`, {
1537
+ body: JSON.stringify(requestBody),
1538
+ headers: { "Content-Type": "application/json" },
1539
+ method: "POST",
1540
+ signal: params.signal
1541
+ });
1542
+ if (!response.ok) {
1543
+ const errorText = await response.text();
1544
+ throw new Error(`Ollama API error ${response.status}: ${errorText}`);
1545
+ }
1546
+ if (!response.body) {
1547
+ throw new Error("Ollama API returned no response body");
1548
+ }
1549
+ yield* parseNDJSONStream(response.body, params.signal);
1550
+ };
1551
+ var ollama = (config = {}) => {
1552
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL5;
1553
+ return {
1554
+ stream: (params) => fetchAndStream2(baseUrl, params)
1555
+ };
1556
+ };
1557
+
1558
+ // src/plugins/aiChat.ts
1559
+ import { Elysia } from "elysia";
1560
+
1561
+ // src/ai/memoryStore.ts
1562
+ var createMemoryStore = () => {
1563
+ const conversations = new Map;
1564
+ const get = async (id) => conversations.get(id);
1565
+ const getOrCreate = async (id) => {
1566
+ let conversation = conversations.get(id);
1567
+ if (!conversation) {
1568
+ conversation = { createdAt: Date.now(), id, messages: [] };
1569
+ conversations.set(id, conversation);
1570
+ }
1571
+ return conversation;
1572
+ };
1573
+ const set = async (id, conversation) => {
1574
+ conversations.set(id, conversation);
1575
+ };
1576
+ const list = async () => Array.from(conversations.values()).map((conv) => ({
1577
+ createdAt: conv.createdAt,
1578
+ id: conv.id,
1579
+ lastMessageAt: conv.lastMessageAt,
1580
+ messageCount: conv.messages.length,
1581
+ title: conv.title ?? "Untitled"
1582
+ })).sort((first, second) => (second.lastMessageAt ?? second.createdAt) - (first.lastMessageAt ?? first.createdAt));
1583
+ const remove = async (id) => {
1584
+ conversations.delete(id);
1585
+ };
1586
+ return { get, getOrCreate, list, remove, set };
1587
+ };
1588
+
1589
+ // types/typeGuards.ts
1590
+ var isValidAIClientMessage = (data) => {
1591
+ if (!data || typeof data !== "object") {
1592
+ return false;
1593
+ }
1594
+ if (!("type" in data) || typeof data.type !== "string") {
1595
+ return false;
1596
+ }
1597
+ switch (data.type) {
1598
+ case "message":
1599
+ return "content" in data && typeof data.content === "string";
1600
+ case "cancel":
1601
+ return "conversationId" in data && typeof data.conversationId === "string";
1602
+ case "branch":
1603
+ return "messageId" in data && typeof data.messageId === "string" && "content" in data && typeof data.content === "string" && "conversationId" in data && typeof data.conversationId === "string";
1604
+ default:
1605
+ return false;
1606
+ }
1607
+ };
1608
+ var isValidAIServerMessage = (data) => {
1609
+ if (!data || typeof data !== "object") {
1610
+ return false;
1611
+ }
1612
+ if (!("type" in data) || typeof data.type !== "string") {
1613
+ return false;
1614
+ }
1615
+ switch (data.type) {
1616
+ case "chunk":
1617
+ case "thinking":
1618
+ return "content" in data && typeof data.content === "string" && "messageId" in data && "conversationId" in data;
1619
+ case "tool_status":
1620
+ return "name" in data && "status" in data && "messageId" in data && "conversationId" in data;
1621
+ case "image":
1622
+ return "data" in data && typeof data.data === "string" && "format" in data && typeof data.format === "string" && "isPartial" in data && typeof data.isPartial === "boolean" && "messageId" in data && "conversationId" in data;
1623
+ case "complete":
1624
+ return "messageId" in data && "conversationId" in data;
1625
+ case "rag_retrieved":
1626
+ return "conversationId" in data && "messageId" in data && "sources" in data && Array.isArray(data.sources);
1627
+ case "error":
1628
+ return "message" in data && typeof data.message === "string";
1629
+ default:
1630
+ return false;
1631
+ }
1632
+ };
1633
+
1634
+ // src/ai/protocol.ts
1635
+ var generateId = () => crypto.randomUUID();
1636
+ var parseAIMessage = (raw) => {
1637
+ if (raw === null || raw === undefined) {
1638
+ return null;
1639
+ }
1640
+ let text;
1641
+ if (typeof raw === "string") {
1642
+ text = raw;
1643
+ } else if (raw instanceof ArrayBuffer) {
1644
+ text = new TextDecoder().decode(raw);
1645
+ } else if (ArrayBuffer.isView(raw)) {
1646
+ text = new TextDecoder().decode(raw);
1647
+ } else if (typeof raw === "object") {
1648
+ if (isValidAIClientMessage(raw)) {
1649
+ return raw;
1650
+ }
1651
+ return null;
1652
+ } else {
1653
+ return null;
1654
+ }
1655
+ try {
1656
+ const parsed = JSON.parse(text);
1657
+ if (isValidAIClientMessage(parsed)) {
1658
+ return parsed;
1659
+ }
1660
+ return null;
1661
+ } catch {
1662
+ return null;
1663
+ }
1664
+ };
1665
+ var serializeAIMessage = (message) => JSON.stringify(message);
1666
+
1667
+ // src/ai/streamAI.ts
1668
+ var WS_OPEN = 1;
1669
+ var BACKPRESSURE_THRESHOLD = 1048576;
1670
+ var BACKPRESSURE_DELAY = 10;
1671
+ var DEFAULT_MAX_TURNS = 10;
1672
+ var DEFAULT_THINKING_BUDGET = 1e4;
1673
+ var INITIAL_TURN = 0;
1674
+ var delay = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
1675
+ var checkBackpressure = async (socket) => {
1676
+ if (!("raw" in socket)) {
1677
+ return;
1678
+ }
1679
+ const { raw } = socket;
1680
+ if (raw && typeof raw === "object" && "bufferedAmount" in raw && typeof raw.bufferedAmount === "number" && raw.bufferedAmount > BACKPRESSURE_THRESHOLD) {
1681
+ await delay(BACKPRESSURE_DELAY);
1682
+ }
1683
+ };
1684
+ var sendMessage = async (socket, message) => {
1685
+ if (socket.readyState !== WS_OPEN) {
1686
+ return false;
1687
+ }
1688
+ await checkBackpressure(socket);
1689
+ socket.send(serializeAIMessage(message));
1690
+ return true;
1691
+ };
1692
+ var buildToolDefinitions = (tools) => Object.entries(tools).map(([name, def]) => ({
1693
+ description: def.description,
1694
+ input_schema: def.input,
1695
+ name
1696
+ }));
1697
+ var extractTextContent = (chunk, onChunk) => {
1698
+ if (!onChunk) {
1699
+ return chunk.content;
1700
+ }
1701
+ const transformed = onChunk(chunk);
1702
+ if (transformed && typeof transformed === "object" && "content" in transformed) {
1703
+ return transformed.content;
1704
+ }
1705
+ return chunk.content;
1706
+ };
1707
+ var sendImageMessage = async (socket, chunk, messageId, conversationId) => sendMessage(socket, {
1708
+ conversationId,
1709
+ data: chunk.data,
1710
+ format: chunk.format,
1711
+ imageId: chunk.imageId,
1712
+ isPartial: chunk.isPartial,
1713
+ messageId,
1714
+ revisedPrompt: chunk.revisedPrompt,
1715
+ type: "image"
1716
+ });
1717
+ var sendToolRunning = async (socket, toolName, toolInput, messageId, conversationId) => sendMessage(socket, {
1718
+ conversationId,
1719
+ input: toolInput,
1720
+ messageId,
1721
+ name: toolName,
1722
+ status: "running",
1723
+ type: "tool_status"
1724
+ });
1725
+ var sendToolComplete = async (socket, toolName, result, messageId, conversationId) => sendMessage(socket, {
1726
+ conversationId,
1727
+ messageId,
1728
+ name: toolName,
1729
+ result,
1730
+ status: "complete",
1731
+ type: "tool_status"
1732
+ });
1733
+ var executeTool = async (options, toolName, toolInput) => {
1734
+ const toolDef = options.tools?.[toolName];
1735
+ if (!toolDef) {
1736
+ return `Error: unknown tool "${toolName}"`;
1737
+ }
1738
+ try {
1739
+ return await toolDef.handler(toolInput);
1740
+ } catch (err) {
1741
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
1742
+ }
1743
+ };
1744
+ var serializeToolCall = (name, input) => `${name}:${JSON.stringify(input)}`;
1745
+ var handleToolChunkText = (chunk, state, options, socket, messageId, conversationId) => {
1746
+ const textContent = extractTextContent(chunk, options.onChunk);
1747
+ state.currentFullResponse += textContent;
1748
+ sendMessage(socket, {
1749
+ content: textContent,
1750
+ conversationId,
1751
+ messageId,
1752
+ type: "chunk"
1753
+ });
1754
+ };
1755
+ var handleToolChunkToolUse = (chunk, state) => {
1756
+ state.pendingToolCalls.push({
1757
+ id: chunk.id,
1758
+ input: chunk.input,
1759
+ name: chunk.name
1760
+ });
1761
+ };
1762
+ var flushThinking = (state) => {
1763
+ if (state.currentThinking) {
1764
+ state.contentBlocks.push({
1765
+ signature: state.currentThinking.signature || undefined,
1766
+ thinking: state.currentThinking.text,
1767
+ type: "thinking"
1768
+ });
1769
+ state.currentThinking = null;
1770
+ }
1771
+ };
1772
+ var processToolChunk = (chunk, state, options, socket, messageId, conversationId) => {
1773
+ let hitAnotherTool = false;
1774
+ switch (chunk.type) {
1775
+ case "thinking":
1776
+ if (chunk.content) {
1777
+ sendMessage(socket, {
1778
+ content: chunk.content,
1779
+ conversationId,
1780
+ messageId,
1781
+ type: "thinking"
1782
+ });
1783
+ }
1784
+ if (!state.currentThinking) {
1785
+ state.currentThinking = { signature: "", text: "" };
1786
+ }
1787
+ state.currentThinking.text += chunk.content;
1788
+ if (chunk.signature) {
1789
+ state.currentThinking.signature = chunk.signature;
1790
+ }
1791
+ break;
1792
+ case "text":
1793
+ flushThinking(state);
1794
+ handleToolChunkText(chunk, state, options, socket, messageId, conversationId);
1795
+ state.contentBlocks.push({
1796
+ content: chunk.content,
1797
+ type: "text"
1798
+ });
1799
+ break;
1800
+ case "image":
1801
+ sendImageMessage(socket, chunk, messageId, conversationId);
1802
+ options.onImage?.({
1803
+ data: chunk.data,
1804
+ format: chunk.format,
1805
+ imageId: chunk.imageId,
1806
+ isPartial: chunk.isPartial,
1807
+ revisedPrompt: chunk.revisedPrompt
1808
+ });
1809
+ break;
1810
+ case "tool_use":
1811
+ flushThinking(state);
1812
+ handleToolChunkToolUse(chunk, state);
1813
+ state.contentBlocks.push({
1814
+ id: chunk.id,
1815
+ input: typeof chunk.input === "object" && chunk.input !== null ? chunk.input : {},
1816
+ name: chunk.name,
1817
+ type: "tool_use"
1818
+ });
1819
+ hitAnotherTool = true;
1820
+ break;
1821
+ case "done":
1822
+ flushThinking(state);
1823
+ state.currentUsage = chunk.usage;
1824
+ break;
1825
+ }
1826
+ return hitAnotherTool;
1827
+ };
1828
+ var processToolTurn = async (socket, options, state, messageId, conversationId, signal) => {
1829
+ const toolCalls = [...state.pendingToolCalls];
1830
+ state.pendingToolCalls = [];
1831
+ state.currentMessages.push({
1832
+ content: state.contentBlocks,
1833
+ role: "assistant"
1834
+ });
1835
+ state.contentBlocks = [];
1836
+ const toolResultBlocks = [];
1837
+ for (const toolCall of toolCalls) {
1838
+ await sendToolRunning(socket, toolCall.name, toolCall.input, messageId, conversationId);
1839
+ const result = await executeTool(options, toolCall.name, toolCall.input);
1840
+ await sendToolComplete(socket, toolCall.name, result, messageId, conversationId);
1841
+ options.onToolUse?.(toolCall.name, toolCall.input, result);
1842
+ toolResultBlocks.push({
1843
+ content: result,
1844
+ tool_use_id: toolCall.id,
1845
+ type: "tool_result"
1846
+ });
1847
+ state.executedToolKeys.add(serializeToolCall(toolCall.name, toolCall.input));
1848
+ }
1849
+ state.currentMessages.push({
1850
+ content: toolResultBlocks,
1851
+ role: "user"
1852
+ });
1853
+ const toolDefs = options.tools ? buildToolDefinitions(options.tools) : undefined;
1854
+ const thinkingConfig = options.thinking ? {
1855
+ budget_tokens: typeof options.thinking === "object" ? options.thinking.budgetTokens : DEFAULT_THINKING_BUDGET,
1856
+ type: "enabled"
1857
+ } : undefined;
1858
+ const stream = options.provider.stream({
1859
+ messages: state.currentMessages,
1860
+ model: options.model,
1861
+ signal,
1862
+ systemPrompt: options.systemPrompt,
1863
+ thinking: thinkingConfig,
1864
+ tools: toolDefs
1865
+ });
1866
+ await consumeToolStream(stream, state, options, socket, messageId, conversationId, signal);
1867
+ };
1868
+ var consumeToolStream = async (stream, state, options, socket, messageId, conversationId, signal) => {
1869
+ let hitAnotherTool = false;
1870
+ for await (const chunk of stream) {
1871
+ if (signal.aborted)
1872
+ break;
1873
+ const isToolHit = processToolChunk(chunk, state, options, socket, messageId, conversationId);
1874
+ if (isToolHit)
1875
+ hitAnotherTool = true;
1876
+ }
1877
+ return hitAnotherTool;
1878
+ };
1879
+ var shouldContinueToolLoop = (state, maxTurns, signal) => state.pendingToolCalls.length > 0 && state.currentTurn < maxTurns && !signal.aborted;
1880
+ var areAllRepeatedToolCalls = (state) => state.pendingToolCalls.every((toolCall) => state.executedToolKeys.has(serializeToolCall(toolCall.name, toolCall.input)));
1881
+ var buildToolLoopResult = (state) => ({
1882
+ fullResponse: state.currentFullResponse,
1883
+ usage: state.currentUsage
1884
+ });
1885
+ var executeToolLoop = async (socket, options, messages, initialToolCalls, initialContentBlocks, messageId, conversationId, signal, fullResponse, turn) => {
1886
+ const maxTurns = options.maxTurns ?? DEFAULT_MAX_TURNS;
1887
+ const state = {
1888
+ contentBlocks: initialContentBlocks,
1889
+ currentFullResponse: fullResponse,
1890
+ currentMessages: [...messages],
1891
+ currentThinking: null,
1892
+ currentTurn: turn,
1893
+ currentUsage: undefined,
1894
+ executedToolKeys: new Set,
1895
+ pendingToolCalls: initialToolCalls
1896
+ };
1897
+ while (shouldContinueToolLoop(state, maxTurns, signal)) {
1898
+ if (areAllRepeatedToolCalls(state))
1899
+ break;
1900
+ await processToolTurn(socket, options, state, messageId, conversationId, signal);
1901
+ state.currentTurn++;
1902
+ }
1903
+ return buildToolLoopResult(state);
1904
+ };
1905
+ var sendComplete = async (socket, messageId, conversationId, usage, durationMs, model, completeMeta) => sendMessage(socket, {
1906
+ conversationId,
1907
+ durationMs,
1908
+ messageId,
1909
+ model,
1910
+ sources: completeMeta?.sources,
1911
+ type: "complete",
1912
+ usage
1913
+ });
1914
+ var sendError = async (socket, err, messageId, conversationId) => sendMessage(socket, {
1915
+ conversationId,
1916
+ message: err instanceof Error ? err.message : String(err),
1917
+ messageId,
1918
+ type: "error"
1919
+ });
1920
+ var handleTextChunk = async (chunk, options, socket, messageId, conversationId) => {
1921
+ const textContent = extractTextContent(chunk, options.onChunk);
1922
+ await sendMessage(socket, {
1923
+ content: textContent,
1924
+ conversationId,
1925
+ messageId,
1926
+ type: "chunk"
1927
+ });
1928
+ return textContent;
1929
+ };
1930
+ var handleToolCalls = async (socket, options, messages, toolCalls, contentBlocks, messageId, conversationId, signal, fullResponse, startTime) => {
1931
+ const toolResult = await executeToolLoop(socket, options, messages, toolCalls, contentBlocks, messageId, conversationId, signal, fullResponse, INITIAL_TURN);
1932
+ await sendComplete(socket, messageId, conversationId, toolResult.usage, Date.now() - startTime, options.model, options.completeMeta);
1933
+ options.onComplete?.(toolResult.fullResponse, toolResult.usage, options.completeMeta);
1934
+ return toolResult;
1935
+ };
1936
+ var processStreamTextChunk = async (chunk, options, socket, messageId, conversationId) => {
1937
+ const textContent = await handleTextChunk(chunk, options, socket, messageId, conversationId);
1938
+ return textContent;
1939
+ };
1940
+ var processStream = async (socket, options, messages, messageId, conversationId, signal, startTime) => {
1941
+ const toolDefs = options.tools ? buildToolDefinitions(options.tools) : undefined;
1942
+ const thinkingConfig = options.thinking ? {
1943
+ budget_tokens: typeof options.thinking === "object" ? options.thinking.budgetTokens : DEFAULT_THINKING_BUDGET,
1944
+ type: "enabled"
1945
+ } : undefined;
1946
+ const stream = options.provider.stream({
1947
+ messages,
1948
+ model: options.model,
1949
+ signal,
1950
+ systemPrompt: options.systemPrompt,
1951
+ thinking: thinkingConfig,
1952
+ tools: toolDefs
1953
+ });
1954
+ const result = await consumeStream(stream, options, socket, messages, messageId, conversationId, signal);
1955
+ if (result.pendingToolCalls.length > 0) {
1956
+ await handleToolCalls(socket, options, messages, result.pendingToolCalls, result.contentBlocks, messageId, conversationId, signal, result.fullResponse, startTime);
1957
+ } else {
1958
+ await sendComplete(socket, messageId, conversationId, result.usage, Date.now() - startTime, options.model, options.completeMeta);
1959
+ options.onComplete?.(result.fullResponse, result.usage, options.completeMeta);
1960
+ }
1961
+ };
1962
+ var flushStreamThinking = (state) => {
1963
+ if (state.currentThinking) {
1964
+ state.contentBlocks.push({
1965
+ signature: state.currentThinking.signature || undefined,
1966
+ thinking: state.currentThinking.text,
1967
+ type: "thinking"
1968
+ });
1969
+ state.currentThinking = null;
1970
+ }
1971
+ };
1972
+ var consumeStreamChunk = async (chunk, options, socket, state, messageId, conversationId) => {
1973
+ switch (chunk.type) {
1974
+ case "thinking":
1975
+ if (chunk.content) {
1976
+ await sendMessage(socket, {
1977
+ content: chunk.content,
1978
+ conversationId,
1979
+ messageId,
1980
+ type: "thinking"
1981
+ });
1982
+ }
1983
+ if (!state.currentThinking) {
1984
+ state.currentThinking = { signature: "", text: "" };
1985
+ }
1986
+ state.currentThinking.text += chunk.content;
1987
+ if (chunk.signature) {
1988
+ state.currentThinking.signature = chunk.signature;
1989
+ }
1990
+ break;
1991
+ case "text":
1992
+ flushStreamThinking(state);
1993
+ state.fullResponse += await processStreamTextChunk(chunk, options, socket, messageId, conversationId);
1994
+ state.contentBlocks.push({ content: chunk.content, type: "text" });
1995
+ break;
1996
+ case "image":
1997
+ await sendImageMessage(socket, chunk, messageId, conversationId);
1998
+ options.onImage?.({
1999
+ data: chunk.data,
2000
+ format: chunk.format,
2001
+ imageId: chunk.imageId,
2002
+ isPartial: chunk.isPartial,
2003
+ revisedPrompt: chunk.revisedPrompt
2004
+ });
2005
+ break;
2006
+ case "tool_use":
2007
+ flushStreamThinking(state);
2008
+ state.pendingToolCalls.push({
2009
+ id: chunk.id,
2010
+ input: chunk.input,
2011
+ name: chunk.name
2012
+ });
2013
+ state.contentBlocks.push({
2014
+ id: chunk.id,
2015
+ input: typeof chunk.input === "object" && chunk.input !== null ? chunk.input : {},
2016
+ name: chunk.name,
2017
+ type: "tool_use"
2018
+ });
2019
+ break;
2020
+ case "done":
2021
+ flushStreamThinking(state);
2022
+ state.usage = chunk.usage;
2023
+ break;
2024
+ }
2025
+ };
2026
+ var consumeStream = async (stream, options, socket, messages, messageId, conversationId, signal) => {
2027
+ const state = {
2028
+ contentBlocks: [],
2029
+ currentThinking: null,
2030
+ fullResponse: "",
2031
+ pendingToolCalls: [],
2032
+ usage: undefined
2033
+ };
2034
+ for await (const chunk of stream) {
2035
+ if (signal.aborted)
2036
+ break;
2037
+ await consumeStreamChunk(chunk, options, socket, state, messageId, conversationId);
2038
+ }
2039
+ return state;
2040
+ };
2041
+ var streamAI = async (socket, conversationId, messageId, options) => {
2042
+ const signal = options.signal ?? new AbortController().signal;
2043
+ const startTime = Date.now();
2044
+ const messages = options.messages ? [...options.messages] : [];
2045
+ try {
2046
+ await processStream(socket, options, messages, messageId, conversationId, signal, startTime);
2047
+ } catch (err) {
2048
+ await handleStreamError(socket, err, messageId, conversationId, signal, startTime);
2049
+ }
2050
+ };
2051
+ var handleStreamError = async (socket, err, messageId, conversationId, signal, startTime) => {
2052
+ if (signal.aborted) {
2053
+ await sendComplete(socket, messageId, conversationId, undefined, Date.now() - startTime);
2054
+ return;
2055
+ }
2056
+ await sendError(socket, err, messageId, conversationId);
2057
+ };
2058
+
2059
+ // src/ai/streamAIToSSE.ts
2060
+ var DEFAULT_MAX_TURNS2 = 10;
2061
+ var DEFAULT_THINKING_BUDGET2 = 1e4;
2062
+ var buildToolDefinitions2 = (tools) => Object.entries(tools).map(([name, def]) => ({
2063
+ description: def.description,
2064
+ input_schema: def.input,
2065
+ name
2066
+ }));
2067
+ var executeTool2 = async (options, toolName, toolInput) => {
2068
+ const toolDef = options.tools?.[toolName];
2069
+ if (!toolDef) {
2070
+ return `Error: unknown tool "${toolName}"`;
2071
+ }
2072
+ try {
2073
+ return await toolDef.handler(toolInput);
2074
+ } catch (err) {
2075
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
2076
+ }
2077
+ };
2078
+ var buildThinkingConfig = (options) => options.thinking ? {
2079
+ budget_tokens: typeof options.thinking === "object" ? options.thinking.budgetTokens : DEFAULT_THINKING_BUDGET2,
2080
+ type: "enabled"
2081
+ } : undefined;
2082
+ var serializeToolCall2 = (name, input) => `${name}:${JSON.stringify(input)}`;
2083
+ var streamAIToSSE = async function* (conversationId, messageId, options, renderers) {
2084
+ const signal = options.signal ?? new AbortController().signal;
2085
+ const startTime = Date.now();
2086
+ const maxTurns = options.maxTurns ?? DEFAULT_MAX_TURNS2;
2087
+ const messages = options.messages ? [...options.messages] : [];
2088
+ try {
2089
+ yield* streamTurns(options, renderers, messages, signal, startTime, maxTurns);
2090
+ } catch (err) {
2091
+ if (signal.aborted)
2092
+ return;
2093
+ yield {
2094
+ data: renderers.error(err instanceof Error ? err.message : String(err)),
2095
+ event: "status"
2096
+ };
2097
+ }
2098
+ };
2099
+ var flushThinking2 = (thinking, contentBlocks) => {
2100
+ contentBlocks.push({
2101
+ signature: thinking.signature || undefined,
2102
+ thinking: thinking.text,
2103
+ type: "thinking"
2104
+ });
2105
+ };
2106
+ var yieldCompletion = (renderers, options, fullResponse, usage, startTime) => {
2107
+ const durationMs = Date.now() - startTime;
2108
+ options.onComplete?.(fullResponse, usage);
2109
+ return {
2110
+ data: renderers.complete(usage, durationMs, options.model),
2111
+ event: "status"
2112
+ };
2113
+ };
2114
+ var processThinkingChunk = function* (content, signature, chunkState, renderers) {
2115
+ chunkState.currentThinking ??= { signature: "", text: "" };
2116
+ chunkState.currentThinking.text += content;
2117
+ chunkState.currentThinking.signature = signature ?? chunkState.currentThinking.signature;
2118
+ yield {
2119
+ data: renderers.thinking(chunkState.currentThinking.text),
2120
+ event: "thinking"
2121
+ };
2122
+ };
2123
+ var maybeFlushThinking = (chunkState) => {
2124
+ if (!chunkState.currentThinking)
2125
+ return;
2126
+ flushThinking2(chunkState.currentThinking, chunkState.contentBlocks);
2127
+ chunkState.currentThinking = null;
2128
+ };
2129
+ var processTextChunk = function* (content, chunkState, renderers, fullResponse) {
2130
+ maybeFlushThinking(chunkState);
2131
+ chunkState.contentBlocks.push({
2132
+ content,
2133
+ type: "text"
2134
+ });
2135
+ yield {
2136
+ data: renderers.chunk(content, fullResponse + content),
2137
+ event: "content"
2138
+ };
2139
+ };
2140
+ var processImageChunk = function* (chunk, renderers, options) {
2141
+ yield {
2142
+ data: renderers.image(chunk.data, chunk.format, chunk.revisedPrompt),
2143
+ event: "images"
2144
+ };
2145
+ options.onImage?.({
2146
+ data: chunk.data,
2147
+ format: chunk.format,
2148
+ imageId: chunk.imageId,
2149
+ isPartial: chunk.isPartial,
2150
+ revisedPrompt: chunk.revisedPrompt
2151
+ });
2152
+ };
2153
+ var processToolUseChunk = (chunk, chunkState) => {
2154
+ maybeFlushThinking(chunkState);
2155
+ chunkState.pendingToolCalls.push({
2156
+ id: chunk.id,
2157
+ input: chunk.input,
2158
+ name: chunk.name
2159
+ });
2160
+ chunkState.contentBlocks.push({
2161
+ id: chunk.id,
2162
+ input: typeof chunk.input === "object" && chunk.input !== null ? chunk.input : {},
2163
+ name: chunk.name,
2164
+ type: "tool_use"
2165
+ });
2166
+ };
2167
+ var processChunk2 = function* (chunk, chunkState, renderers, options, fullResponse) {
2168
+ switch (chunk.type) {
2169
+ case "thinking":
2170
+ yield* processThinkingChunk(chunk.content, chunk.signature, chunkState, renderers);
2171
+ break;
2172
+ case "text":
2173
+ yield* processTextChunk(chunk.content, chunkState, renderers, fullResponse);
2174
+ break;
2175
+ case "image":
2176
+ yield* processImageChunk(chunk, renderers, options);
2177
+ break;
2178
+ case "tool_use":
2179
+ processToolUseChunk(chunk, chunkState);
2180
+ break;
2181
+ case "done":
2182
+ maybeFlushThinking(chunkState);
2183
+ chunkState.usage = chunk.usage;
2184
+ break;
2185
+ }
2186
+ };
2187
+ var executeToolCalls = async function* (pendingToolCalls, options, renderers, turnState) {
2188
+ const toolResultBlocks = [];
2189
+ for (const toolCall of pendingToolCalls) {
2190
+ turnState.allToolsHtml += renderers.toolRunning(toolCall.name, toolCall.input);
2191
+ yield { data: turnState.allToolsHtml, event: "tools" };
2192
+ const result = await executeTool2(options, toolCall.name, toolCall.input);
2193
+ turnState.allToolsHtml = turnState.allToolsHtml.replace(renderers.toolRunning(toolCall.name, toolCall.input), renderers.toolComplete(toolCall.name, result));
2194
+ yield { data: turnState.allToolsHtml, event: "tools" };
2195
+ options.onToolUse?.(toolCall.name, toolCall.input, result);
2196
+ toolResultBlocks.push({
2197
+ content: result,
2198
+ tool_use_id: toolCall.id,
2199
+ type: "tool_result"
2200
+ });
2201
+ turnState.executedToolKeys.add(serializeToolCall2(toolCall.name, toolCall.input));
2202
+ }
2203
+ return toolResultBlocks;
2204
+ };
2205
+ var consumeStream2 = async function* (stream, chunkState, renderers, options, turnState, signal) {
2206
+ for await (const chunk of stream) {
2207
+ if (signal.aborted)
2208
+ break;
2209
+ const prevResponse = turnState.fullResponse;
2210
+ yield* processChunk2(chunk, chunkState, renderers, options, turnState.fullResponse);
2211
+ if (chunk.type !== "text")
2212
+ continue;
2213
+ turnState.fullResponse = prevResponse + chunk.content;
2214
+ }
2215
+ };
2216
+ var shouldStopToolLoop = (chunkState, turnState, signal) => {
2217
+ if (chunkState.pendingToolCalls.length === 0 || signal.aborted) {
2218
+ return true;
2219
+ }
2220
+ return chunkState.pendingToolCalls.every((toolCall) => turnState.executedToolKeys.has(serializeToolCall2(toolCall.name, toolCall.input)));
2221
+ };
2222
+ var processTurn = async function* (chunkState, options, renderers, turnState) {
2223
+ turnState.currentMessages.push({
2224
+ content: chunkState.contentBlocks,
2225
+ role: "assistant"
2226
+ });
2227
+ const toolResults = yield* executeToolCalls(chunkState.pendingToolCalls, options, renderers, turnState);
2228
+ turnState.currentMessages.push({
2229
+ content: toolResults,
2230
+ role: "user"
2231
+ });
2232
+ };
2233
+ var streamTurns = async function* (options, renderers, messages, signal, startTime, maxTurns) {
2234
+ const turnState = {
2235
+ allToolsHtml: "",
2236
+ currentMessages: [...messages],
2237
+ executedToolKeys: new Set,
2238
+ fullResponse: "",
2239
+ turn: 0
2240
+ };
2241
+ const toolDefs = options.tools ? buildToolDefinitions2(options.tools) : undefined;
2242
+ const thinkingConfig = buildThinkingConfig(options);
2243
+ for (;turnState.turn <= maxTurns && !signal.aborted; turnState.turn++) {
2244
+ const chunkState = {
2245
+ contentBlocks: [],
2246
+ currentThinking: null,
2247
+ pendingToolCalls: [],
2248
+ usage: undefined
2249
+ };
2250
+ const stream = options.provider.stream({
2251
+ messages: turnState.currentMessages,
2252
+ model: options.model,
2253
+ signal,
2254
+ systemPrompt: options.systemPrompt,
2255
+ thinking: thinkingConfig,
2256
+ tools: toolDefs
2257
+ });
2258
+ yield* consumeStream2(stream, chunkState, renderers, options, turnState, signal);
2259
+ if (shouldStopToolLoop(chunkState, turnState, signal)) {
2260
+ return void (yield yieldCompletion(renderers, options, turnState.fullResponse, chunkState.usage, startTime));
2261
+ }
2262
+ yield* processTurn(chunkState, options, renderers, turnState);
2263
+ }
2264
+ return;
2265
+ };
2266
+
2267
+ // src/constants.ts
2268
+ var EXCLUDE_LAST_OFFSET = -1;
2269
+ var HOURS_IN_DAY = 24;
2270
+ var MILLISECONDS_IN_A_SECOND = 1000;
2271
+ var MINUTES_IN_AN_HOUR = 60;
2272
+ var SECONDS_IN_A_MINUTE = 60;
2273
+ var MILLISECONDS_IN_A_MINUTE = MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE;
2274
+ var MILLISECONDS_IN_A_DAY = MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE * MINUTES_IN_AN_HOUR * HOURS_IN_DAY;
2275
+ var TWO_THIRDS = 2 / 3;
2276
+
2277
+ // src/ai/htmxRenderers.ts
2278
+ var escapeHtml = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2279
+ var defaultChunk = (_text, fullContent) => `<div class="ai-content">${escapeHtml(fullContent)}</div>`;
2280
+ var defaultMessageStart = ({
2281
+ cancelUrl,
2282
+ content,
2283
+ messageId,
2284
+ sseUrl
2285
+ }) => `<div id="msg-${messageId}" class="message user">` + `<div>${escapeHtml(content)}</div>` + `</div>` + `<div id="response-${messageId}" ` + `hx-ext="sse" ` + `sse-connect="${escapeHtml(sseUrl)}" ` + `hx-swap="innerHTML">` + `<div id="sse-retrieval-${messageId}" sse-swap="retrieval" hx-swap="innerHTML"></div>` + `<div id="sse-sources-${messageId}" sse-swap="sources" hx-swap="innerHTML"></div>` + `<div id="sse-content-${messageId}" sse-swap="content" hx-swap="innerHTML"></div>` + `<div id="sse-thinking-${messageId}" sse-swap="thinking" hx-swap="innerHTML"></div>` + `<div id="sse-tools-${messageId}" sse-swap="tools" hx-swap="innerHTML"></div>` + `<div id="sse-images-${messageId}" sse-swap="images" hx-swap="innerHTML"></div>` + `<div id="sse-status-${messageId}" sse-swap="status" hx-swap="innerHTML">` + `<button type="button" class="ai-cancel-button" hx-post="${escapeHtml(cancelUrl)}" hx-target="#sse-status-${messageId}" hx-swap="innerHTML">Cancel</button>` + `</div>` + `</div>`;
2286
+ var defaultThinking = (text) => `<details class="ai-thinking"><summary>Thinking</summary><p>${escapeHtml(text)}</p></details>`;
2287
+ var defaultToolRunning = (name, _input) => `<div class="ai-tool running"><span class="tool-name">${escapeHtml(name)}</span> <span class="tool-status">Running...</span></div>`;
2288
+ var defaultToolComplete = (name, result) => `<details class="ai-tool complete"><summary>${escapeHtml(name)}</summary><pre>${escapeHtml(result)}</pre></details>`;
2289
+ var defaultImage = (data, format, revisedPrompt) => `<figure class="ai-image">` + `<img src="data:image/${escapeHtml(format)};base64,${data}" alt="${revisedPrompt ? escapeHtml(revisedPrompt) : "Generated image"}" />${revisedPrompt ? `<figcaption>${escapeHtml(revisedPrompt)}</figcaption>` : ""}</figure>`;
2290
+ var defaultComplete = (usage, durationMs, model) => {
2291
+ const parts = [];
2292
+ if (model) {
2293
+ parts.push(escapeHtml(model));
2294
+ }
2295
+ if (usage) {
2296
+ parts.push(`${usage.inputTokens}in / ${usage.outputTokens}out`);
2297
+ }
2298
+ if (durationMs !== undefined) {
2299
+ const seconds = (durationMs / MILLISECONDS_IN_A_SECOND).toFixed(1);
2300
+ parts.push(`${seconds}s`);
2301
+ }
2302
+ return parts.length > 0 ? `<div class="ai-usage">${parts.join(" \xB7 ")}</div>` : "";
2303
+ };
2304
+ var defaultError = (message) => `<div class="ai-error">${escapeHtml(message)}</div>`;
2305
+ var defaultCanceled = () => `<div class="ai-canceled">Canceled.</div>`;
2306
+ var defaultRAGRetrieving = () => `<div class="ai-retrieving">Retrieving sources...</div>`;
2307
+ var renderTraceSummary = (trace) => {
2308
+ if (!trace) {
2309
+ return "";
2310
+ }
2311
+ return `<div class="ai-trace-summary">` + `Mode: ${escapeHtml(trace.mode)} \xB7 Final: ${trace.resultCounts.final} \xB7 Vector: ${trace.resultCounts.vector} \xB7 Lexical: ${trace.resultCounts.lexical}` + `</div>`;
2312
+ };
2313
+ var defaultRAGRetrieved = (sources, input) => sources.length === 0 ? "" : `<div class="ai-sources">` + `<h4>Citations</h4>` + renderTraceSummary(input?.trace) + `<ul>` + `${sources.map((source) => `<li>[${source.chunkId}] ${escapeHtml(source.text)}${source.source ? ` (${escapeHtml(source.source)})` : ""}</li>`).join("")}` + `</ul>` + `</div>`;
2314
+ var resolveRenderers = (custom) => ({
2315
+ chunk: custom?.chunk ?? defaultChunk,
2316
+ messageStart: custom?.messageStart ?? defaultMessageStart,
2317
+ complete: custom?.complete ?? defaultComplete,
2318
+ canceled: custom?.canceled ?? defaultCanceled,
2319
+ error: custom?.error ?? defaultError,
2320
+ image: custom?.image ?? defaultImage,
2321
+ ragRetrieving: custom?.ragRetrieving ?? defaultRAGRetrieving,
2322
+ ragRetrieved: custom?.ragRetrieved ?? defaultRAGRetrieved,
2323
+ thinking: custom?.thinking ?? defaultThinking,
2324
+ toolComplete: custom?.toolComplete ?? defaultToolComplete,
2325
+ toolRunning: custom?.toolRunning ?? defaultToolRunning
2326
+ });
2327
+
2328
+ // src/plugins/aiChat.ts
2329
+ var DEFAULT_PATH = "/chat";
2330
+ var MAX_PREFIX_LEN = 12;
2331
+ var TITLE_MAX_LENGTH = 80;
2332
+ var NOT_FOUND2 = -1;
2333
+ var defaultParseProvider = (content) => {
2334
+ const colonIdx = content.indexOf(":");
2335
+ const hasPrefix = colonIdx > 0 && colonIdx < MAX_PREFIX_LEN;
2336
+ return {
2337
+ content: hasPrefix ? content.slice(colonIdx + 1) : content,
2338
+ providerName: hasPrefix ? content.slice(0, colonIdx) : "anthropic"
2339
+ };
2340
+ };
2341
+ var appendMessage = (conversation, message) => {
2342
+ conversation.messages.push(message);
2343
+ conversation.lastMessageAt = Date.now();
2344
+ if (!conversation.title && message.role === "user") {
2345
+ conversation.title = message.content.slice(0, TITLE_MAX_LENGTH);
2346
+ }
2347
+ };
2348
+ var getHistory = (conversation) => conversation.messages.map((msg) => ({
2349
+ content: msg.content,
2350
+ role: msg.role
2351
+ }));
2352
+ var branchConversation = (source, fromMessageId) => {
2353
+ const cutoffIndex = source.messages.findIndex((msg) => msg.id === fromMessageId);
2354
+ if (cutoffIndex === NOT_FOUND2) {
2355
+ return null;
2356
+ }
2357
+ const newId = generateId();
2358
+ const branchedMessages = source.messages.slice(0, cutoffIndex + 1).map((msg) => ({ ...msg, conversationId: newId }));
2359
+ const newConversation = {
2360
+ createdAt: Date.now(),
2361
+ id: newId,
2362
+ messages: branchedMessages
2363
+ };
2364
+ return newConversation;
2365
+ };
2366
+ var buildUserMessage = (content, attachments) => {
2367
+ if (attachments && attachments.length > 0) {
2368
+ return {
2369
+ content: [
2370
+ ...attachments.map((att) => {
2371
+ if (att.media_type === "application/pdf") {
2372
+ return {
2373
+ name: att.name,
2374
+ source: {
2375
+ data: att.data,
2376
+ media_type: att.media_type,
2377
+ type: "base64"
2378
+ },
2379
+ type: "document"
2380
+ };
2381
+ }
2382
+ return {
2383
+ source: {
2384
+ data: att.data,
2385
+ media_type: att.media_type,
2386
+ type: "base64"
2387
+ },
2388
+ type: "image"
2389
+ };
2390
+ }),
2391
+ { content, type: "text" }
2392
+ ],
2393
+ role: "user"
2394
+ };
2395
+ }
2396
+ return { content, role: "user" };
2397
+ };
2398
+ var resolveModel = (config, parsed) => {
2399
+ if (parsed.model) {
2400
+ return parsed.model;
2401
+ }
2402
+ if (typeof config.model === "string") {
2403
+ return config.model;
2404
+ }
2405
+ if (typeof config.model === "function") {
2406
+ return config.model(parsed.providerName);
2407
+ }
2408
+ return parsed.providerName;
2409
+ };
2410
+ var resolveTools = (config, providerName, model) => typeof config.tools === "function" ? config.tools(providerName, model) : config.tools;
2411
+ var resolveThinking = (config, providerName, model) => typeof config.thinking === "function" ? config.thinking(providerName, model) : config.thinking;
2412
+ var aiChat = (config) => {
2413
+ const path = config.path ?? DEFAULT_PATH;
2414
+ const store = config.store ?? createMemoryStore();
2415
+ const parseProvider = config.parseProvider ?? defaultParseProvider;
2416
+ const abortControllers = new Map;
2417
+ const handleCancel = (conversationId) => {
2418
+ const controller = abortControllers.get(conversationId);
2419
+ if (controller) {
2420
+ controller.abort();
2421
+ abortControllers.delete(conversationId);
2422
+ }
2423
+ };
2424
+ const handleBranch = async (ws, messageId, conversationId) => {
2425
+ const source = await store.get(conversationId);
2426
+ if (!source) {
2427
+ return;
2428
+ }
2429
+ const newConv = branchConversation(source, messageId);
2430
+ if (newConv) {
2431
+ await store.set(newConv.id, newConv);
2432
+ ws.send(JSON.stringify({ conversationId: newConv.id, type: "branched" }));
2433
+ }
2434
+ };
2435
+ const handleUserMessage = async (ws, rawContent, rawConversationId, attachments) => {
2436
+ const conversationId = rawConversationId ?? generateId();
2437
+ const messageId = generateId();
2438
+ const parsed = parseProvider(rawContent);
2439
+ const { content, providerName } = parsed;
2440
+ const conversation = await store.getOrCreate(conversationId);
2441
+ const history = getHistory(conversation);
2442
+ const controller = new AbortController;
2443
+ abortControllers.set(conversationId, controller);
2444
+ appendMessage(conversation, {
2445
+ attachments,
2446
+ content,
2447
+ conversationId,
2448
+ id: messageId,
2449
+ role: "user",
2450
+ timestamp: Date.now()
2451
+ });
2452
+ await store.set(conversationId, conversation);
2453
+ const model = resolveModel(config, parsed);
2454
+ const userMessage = buildUserMessage(content, attachments);
2455
+ await streamAI(ws, conversationId, messageId, {
2456
+ maxTurns: config.maxTurns,
2457
+ messages: [...history, userMessage],
2458
+ model,
2459
+ provider: config.provider(providerName),
2460
+ signal: controller.signal,
2461
+ systemPrompt: config.systemPrompt,
2462
+ thinking: resolveThinking(config, providerName, model),
2463
+ tools: resolveTools(config, providerName, model),
2464
+ onComplete: async (fullResponse, usage) => {
2465
+ const conv = await store.get(conversationId);
2466
+ if (conv) {
2467
+ appendMessage(conv, {
2468
+ content: fullResponse,
2469
+ conversationId,
2470
+ id: generateId(),
2471
+ role: "assistant",
2472
+ timestamp: Date.now()
2473
+ });
2474
+ await store.set(conversationId, conv);
2475
+ }
2476
+ abortControllers.delete(conversationId);
2477
+ config.onComplete?.(conversationId, fullResponse, usage);
2478
+ }
2479
+ });
2480
+ };
2481
+ const htmxRoutes = () => {
2482
+ if (!config.htmx) {
2483
+ return new Elysia;
2484
+ }
2485
+ const renderers = resolveRenderers(typeof config.htmx === "object" ? config.htmx.render : undefined);
2486
+ return new Elysia().post(`${path}/message`, async ({ body }) => {
2487
+ const requestBody = body && typeof body === "object" ? body : {};
2488
+ const rawContent = "content" in requestBody ? String(requestBody.content) : undefined;
2489
+ const rawConvId = "conversationId" in requestBody ? String(requestBody.conversationId) : undefined;
2490
+ const rawAttachmentsValue = "attachments" in requestBody ? requestBody.attachments : undefined;
2491
+ const rawAttachments = Array.isArray(rawAttachmentsValue) ? rawAttachmentsValue : undefined;
2492
+ if (!rawContent) {
2493
+ return new Response("Missing content", { status: 400 });
2494
+ }
2495
+ const conversationId = rawConvId || generateId();
2496
+ const messageId = generateId();
2497
+ const parsed = parseProvider(rawContent);
2498
+ const { content } = parsed;
2499
+ const conversation = await store.getOrCreate(conversationId);
2500
+ appendMessage(conversation, {
2501
+ attachments: rawAttachments,
2502
+ content,
2503
+ conversationId,
2504
+ id: messageId,
2505
+ role: "user",
2506
+ timestamp: Date.now()
2507
+ });
2508
+ await store.set(conversationId, conversation);
2509
+ const sseUrl = `${path}/sse/${conversationId}/${messageId}`;
2510
+ return new Response(`<div id="msg-${messageId}" class="message user">` + `<div>${content}</div>` + `</div>` + `<div id="response-${messageId}" ` + `hx-ext="sse" ` + `sse-connect="${sseUrl}" ` + `hx-swap="innerHTML">` + `<div id="sse-content-${messageId}" sse-swap="content" hx-swap="innerHTML"></div>` + `<div id="sse-thinking-${messageId}" sse-swap="thinking" hx-swap="innerHTML"></div>` + `<div id="sse-tools-${messageId}" sse-swap="tools" hx-swap="innerHTML"></div>` + `<div id="sse-images-${messageId}" sse-swap="images" hx-swap="innerHTML"></div>` + `<div id="sse-status-${messageId}" sse-swap="status" hx-swap="innerHTML"></div>` + `</div>`, { headers: { "Content-Type": "text/html" } });
2511
+ }).get(`${path}/sse/:conversationId/:messageId`, async function* ({ params }) {
2512
+ const { conversationId, messageId } = params;
2513
+ const conversation = await store.get(conversationId);
2514
+ if (!conversation) {
2515
+ yield {
2516
+ data: renderers.error("Conversation not found"),
2517
+ event: "status"
2518
+ };
2519
+ return;
2520
+ }
2521
+ const parsed = parseProvider(conversation.messages.at(EXCLUDE_LAST_OFFSET)?.content ?? "");
2522
+ const { providerName } = parsed;
2523
+ const model = resolveModel(config, parsed);
2524
+ const controller = new AbortController;
2525
+ abortControllers.set(conversationId, controller);
2526
+ const history = getHistory(conversation);
2527
+ const lastMsg = conversation.messages.at(EXCLUDE_LAST_OFFSET);
2528
+ const userMessage = buildUserMessage(lastMsg?.content ?? "", lastMsg?.attachments);
2529
+ const sseStream = streamAIToSSE(conversationId, messageId, {
2530
+ maxTurns: config.maxTurns,
2531
+ messages: [
2532
+ ...history.slice(0, EXCLUDE_LAST_OFFSET),
2533
+ userMessage
2534
+ ],
2535
+ model,
2536
+ provider: config.provider(providerName),
2537
+ signal: controller.signal,
2538
+ systemPrompt: config.systemPrompt,
2539
+ thinking: resolveThinking(config, providerName, model),
2540
+ tools: resolveTools(config, providerName, model)
2541
+ }, renderers);
2542
+ for await (const event of sseStream) {
2543
+ yield event;
2544
+ }
2545
+ const conv = await store.get(conversationId);
2546
+ if (conv) {
2547
+ await store.set(conversationId, conv);
2548
+ }
2549
+ abortControllers.delete(conversationId);
2550
+ }).get(`${path}/history/:conversationId`, async ({ params }) => {
2551
+ const conv = await store.get(params.conversationId);
2552
+ if (!conv) {
2553
+ return new Response("", { status: 404 });
2554
+ }
2555
+ const html = conv.messages.map((msg) => `<div class="message ${msg.role}"><div>${msg.content}</div></div>`).join("");
2556
+ return new Response(html, {
2557
+ headers: { "Content-Type": "text/html" }
2558
+ });
2559
+ }).get(`${path}/conversations/list`, async () => {
2560
+ const convos = await store.list();
2561
+ const html = convos.map((c) => `<div class="conversation-item" ` + `hx-get="${path}/history/${c.id}" ` + `hx-target="#messages" ` + `hx-swap="innerHTML">` + `<span class="title">${c.title}</span>` + `<span class="count">${c.messageCount} messages</span>` + `</div>`).join("");
2562
+ return new Response(html || '<div class="empty">No conversations</div>', { headers: { "Content-Type": "text/html" } });
2563
+ });
2564
+ };
2565
+ return new Elysia().ws(path, {
2566
+ message: async (ws, raw) => {
2567
+ const msg = parseAIMessage(raw);
2568
+ if (!msg) {
2569
+ return;
2570
+ }
2571
+ if (msg.type === "cancel" && msg.conversationId) {
2572
+ handleCancel(msg.conversationId);
2573
+ return;
2574
+ }
2575
+ if (msg.type === "branch") {
2576
+ await handleBranch(ws, msg.messageId, msg.conversationId);
2577
+ return;
2578
+ }
2579
+ if (msg.type === "message") {
2580
+ await handleUserMessage(ws, msg.content, msg.conversationId, msg.attachments);
2581
+ }
2582
+ }
2583
+ }).get(`${path}/conversations`, () => store.list()).get(`${path}/conversations/:id`, async ({ params }) => {
2584
+ const conv = await store.get(params.id);
2585
+ if (!conv) {
2586
+ return new Response("Not found", { status: 404 });
2587
+ }
2588
+ return {
2589
+ id: conv.id,
2590
+ messages: conv.messages,
2591
+ title: conv.title ?? "Untitled"
2592
+ };
2593
+ }).delete(`${path}/conversations/:id`, async ({ params }) => {
2594
+ await store.remove(params.id);
2595
+ return { ok: true };
2596
+ }).use(htmxRoutes());
2597
+ };
2598
+ // src/ai/conversationManager.ts
2599
+ var NOT_FOUND3 = -1;
2600
+ var TITLE_MAX_LENGTH2 = 80;
2601
+ var createConversationManager = () => {
2602
+ const conversations = new Map;
2603
+ const getOrCreate = (conversationId) => {
2604
+ const id = conversationId ?? generateId();
2605
+ let conversation = conversations.get(id);
2606
+ if (!conversation) {
2607
+ conversation = { createdAt: Date.now(), id, messages: [] };
2608
+ conversations.set(id, conversation);
2609
+ }
2610
+ return conversation;
2611
+ };
2612
+ const appendMessage2 = (conversationId, message) => {
2613
+ const conversation = getOrCreate(conversationId);
2614
+ conversation.messages.push(message);
2615
+ conversation.lastMessageAt = Date.now();
2616
+ if (!conversation.title && message.role === "user") {
2617
+ conversation.title = message.content.slice(0, TITLE_MAX_LENGTH2);
2618
+ }
2619
+ };
2620
+ const branch = (fromMessageId, sourceConversationId) => {
2621
+ const source = conversations.get(sourceConversationId);
2622
+ if (!source) {
2623
+ return null;
2624
+ }
2625
+ const cutoffIndex = source.messages.findIndex((msg) => msg.id === fromMessageId);
2626
+ if (cutoffIndex === NOT_FOUND3) {
2627
+ return null;
2628
+ }
2629
+ const newId = generateId();
2630
+ const branchedMessages = source.messages.slice(0, cutoffIndex + 1).map((msg) => ({ ...msg, conversationId: newId }));
2631
+ const newConversation = {
2632
+ createdAt: Date.now(),
2633
+ id: newId,
2634
+ messages: branchedMessages
2635
+ };
2636
+ conversations.set(newId, newConversation);
2637
+ return newId;
2638
+ };
2639
+ const emptyHistory = [];
2640
+ const getHistory2 = (conversationId) => {
2641
+ const conversation = conversations.get(conversationId);
2642
+ if (!conversation) {
2643
+ return emptyHistory;
2644
+ }
2645
+ const history = conversation.messages.map((msg) => ({
2646
+ content: msg.content,
2647
+ role: msg.role
2648
+ }));
2649
+ return history;
2650
+ };
2651
+ const getAbortController = (conversationId) => {
2652
+ const conversation = getOrCreate(conversationId);
2653
+ const controller = new AbortController;
2654
+ conversation.activeStreamAbort = controller;
2655
+ return controller;
2656
+ };
2657
+ const abort = (conversationId) => {
2658
+ const conversation = conversations.get(conversationId);
2659
+ if (conversation?.activeStreamAbort) {
2660
+ conversation.activeStreamAbort.abort();
2661
+ conversation.activeStreamAbort = undefined;
2662
+ }
2663
+ };
2664
+ const get = (conversationId) => conversations.get(conversationId);
2665
+ const remove = (conversationId) => conversations.delete(conversationId);
2666
+ const list = () => Array.from(conversations.values()).map((conv) => ({
2667
+ createdAt: conv.createdAt,
2668
+ id: conv.id,
2669
+ lastMessageAt: conv.lastMessageAt,
2670
+ messageCount: conv.messages.length,
2671
+ title: conv.title ?? "Untitled"
2672
+ })).sort((first, second) => (second.lastMessageAt ?? second.createdAt) - (first.lastMessageAt ?? first.createdAt));
2673
+ const getMessages = (conversationId) => {
2674
+ const conversation = conversations.get(conversationId);
2675
+ if (!conversation) {
2676
+ return [];
2677
+ }
2678
+ return conversation.messages;
2679
+ };
2680
+ return {
2681
+ abort,
2682
+ appendMessage: appendMessage2,
2683
+ branch,
2684
+ get,
2685
+ getAbortController,
2686
+ getHistory: getHistory2,
2687
+ getMessages,
2688
+ getOrCreate,
2689
+ list,
2690
+ remove
2691
+ };
2692
+ };
2693
+ // src/ai/client/actions.ts
2694
+ var serverMessageToAction = (message) => {
2695
+ switch (message.type) {
2696
+ case "chunk":
2697
+ return {
2698
+ content: message.content,
2699
+ conversationId: message.conversationId,
2700
+ messageId: message.messageId,
2701
+ type: "chunk"
2702
+ };
2703
+ case "thinking":
2704
+ return {
2705
+ content: message.content,
2706
+ conversationId: message.conversationId,
2707
+ messageId: message.messageId,
2708
+ type: "thinking"
2709
+ };
2710
+ case "tool_status":
2711
+ return {
2712
+ conversationId: message.conversationId,
2713
+ input: message.input,
2714
+ messageId: message.messageId,
2715
+ name: message.name,
2716
+ result: message.result,
2717
+ status: message.status,
2718
+ type: "tool_status"
2719
+ };
2720
+ case "image":
2721
+ return {
2722
+ conversationId: message.conversationId,
2723
+ data: message.data,
2724
+ format: message.format,
2725
+ imageId: message.imageId,
2726
+ isPartial: message.isPartial,
2727
+ messageId: message.messageId,
2728
+ revisedPrompt: message.revisedPrompt,
2729
+ type: "image"
2730
+ };
2731
+ case "complete":
2732
+ return {
2733
+ conversationId: message.conversationId,
2734
+ durationMs: message.durationMs,
2735
+ messageId: message.messageId,
2736
+ model: message.model,
2737
+ sources: message.sources,
2738
+ type: "complete",
2739
+ usage: message.usage
2740
+ };
2741
+ case "rag_retrieving":
2742
+ return {
2743
+ conversationId: message.conversationId,
2744
+ messageId: message.messageId,
2745
+ retrievalStartedAt: message.retrievalStartedAt,
2746
+ type: "rag_retrieving"
2747
+ };
2748
+ case "rag_retrieved":
2749
+ return {
2750
+ conversationId: message.conversationId,
2751
+ messageId: message.messageId,
2752
+ retrievalDurationMs: message.retrievalDurationMs,
2753
+ retrievalStartedAt: message.retrievalStartedAt,
2754
+ retrievedAt: message.retrievedAt,
2755
+ sources: message.sources,
2756
+ trace: message.trace,
2757
+ type: "rag_retrieved"
2758
+ };
2759
+ case "error":
2760
+ return { message: message.message, type: "error" };
2761
+ default:
2762
+ return null;
2763
+ }
2764
+ };
2765
+ // src/ai/client/connection.ts
2766
+ var WS_OPEN2 = 1;
2767
+ var WS_NORMAL_CLOSURE = 1000;
2768
+ var WS_CLOSED = 3;
2769
+ var DEFAULT_PING_INTERVAL = 30000;
2770
+ var RECONNECT_INITIAL_DELAY = 500;
2771
+ var RECONNECT_POLL_INTERVAL = 300;
2772
+ var DEFAULT_MAX_RECONNECT_ATTEMPTS = 60;
2773
+ var noop = () => {};
2774
+ var noopUnsubscribe = () => noop;
2775
+ var NOOP_CONNECTION = {
2776
+ close: noop,
2777
+ send: noop,
2778
+ subscribe: noopUnsubscribe,
2779
+ getReadyState: () => WS_CLOSED
2780
+ };
2781
+ var buildWsUrl = (path) => {
2782
+ const { hostname, port, protocol } = window.location;
2783
+ const wsProtocol = protocol === "https:" ? "wss:" : "ws:";
2784
+ const portSuffix = port ? `:${port}` : "";
2785
+ return `${wsProtocol}//${hostname}${portSuffix}${path}`;
2786
+ };
2787
+ var parseServerMessage = (event) => {
2788
+ let data;
2789
+ try {
2790
+ data = JSON.parse(String(event.data));
2791
+ } catch {
2792
+ return null;
2793
+ }
2794
+ if (data && typeof data === "object" && "type" in data && data.type === "pong") {
2795
+ return null;
2796
+ }
2797
+ if (!isValidAIServerMessage(data)) {
2798
+ return null;
2799
+ }
2800
+ return data;
2801
+ };
2802
+ var createAIConnection = (path, options = {}) => {
2803
+ if (typeof window === "undefined") {
2804
+ return NOOP_CONNECTION;
2805
+ }
2806
+ const shouldReconnect = options.reconnect !== false;
2807
+ const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
2808
+ const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
2809
+ const listeners = new Set;
2810
+ const connState = {
2811
+ isConnected: false,
2812
+ pendingMessages: [],
2813
+ pingInterval: null,
2814
+ reconnectAttempts: 0,
2815
+ reconnectTimeout: null,
2816
+ ws: null
2817
+ };
2818
+ const flushPendingMessages = () => {
2819
+ if (connState.ws?.readyState !== WS_OPEN2) {
2820
+ return;
2821
+ }
2822
+ while (connState.pendingMessages.length > 0) {
2823
+ const next = connState.pendingMessages.shift();
2824
+ if (typeof next === "string") {
2825
+ connState.ws.send(next);
2826
+ }
2827
+ }
2828
+ };
2829
+ const clearTimers = () => {
2830
+ if (connState.pingInterval) {
2831
+ clearInterval(connState.pingInterval);
2832
+ connState.pingInterval = null;
2833
+ }
2834
+ if (connState.reconnectTimeout) {
2835
+ clearTimeout(connState.reconnectTimeout);
2836
+ connState.reconnectTimeout = null;
2837
+ }
2838
+ };
2839
+ const scheduleReconnect = () => {
2840
+ connState.reconnectAttempts++;
2841
+ const delay2 = connState.reconnectAttempts === 1 ? RECONNECT_INITIAL_DELAY : RECONNECT_POLL_INTERVAL;
2842
+ connState.reconnectTimeout = setTimeout(() => {
2843
+ if (connState.reconnectAttempts > maxReconnectAttempts) {
2844
+ return;
2845
+ }
2846
+ connect();
2847
+ }, delay2);
2848
+ };
2849
+ const connect = () => {
2850
+ const url = buildWsUrl(path);
2851
+ const wsInstance = new WebSocket(url, options.protocols);
2852
+ wsInstance.onopen = () => {
2853
+ connState.isConnected = true;
2854
+ connState.reconnectAttempts = 0;
2855
+ flushPendingMessages();
2856
+ connState.pingInterval = setInterval(() => {
2857
+ if (wsInstance.readyState === WS_OPEN2 && connState.isConnected) {
2858
+ wsInstance.send(JSON.stringify({ type: "ping" }));
2859
+ }
2860
+ }, pingInterval);
2861
+ };
2862
+ wsInstance.onmessage = (event) => {
2863
+ const message = parseServerMessage(event);
2864
+ if (!message) {
2865
+ return;
2866
+ }
2867
+ listeners.forEach((listener) => listener(message));
2868
+ };
2869
+ wsInstance.onclose = (event) => {
2870
+ connState.isConnected = false;
2871
+ clearTimers();
2872
+ const shouldAttemptReconnect = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && connState.reconnectAttempts < maxReconnectAttempts;
2873
+ if (shouldAttemptReconnect) {
2874
+ scheduleReconnect();
2875
+ }
2876
+ };
2877
+ wsInstance.onerror = () => {};
2878
+ connState.ws = wsInstance;
2879
+ };
2880
+ const send = (msg) => {
2881
+ const serialized = JSON.stringify(msg);
2882
+ if (connState.ws?.readyState === WS_OPEN2) {
2883
+ connState.ws.send(serialized);
2884
+ return;
2885
+ }
2886
+ connState.pendingMessages.push(serialized);
2887
+ };
2888
+ const subscribe = (callback) => {
2889
+ listeners.add(callback);
2890
+ return () => {
2891
+ listeners.delete(callback);
2892
+ };
2893
+ };
2894
+ const close = () => {
2895
+ clearTimers();
2896
+ if (connState.ws) {
2897
+ connState.ws.close(WS_NORMAL_CLOSURE);
2898
+ connState.ws = null;
2899
+ }
2900
+ connState.isConnected = false;
2901
+ listeners.clear();
2902
+ };
2903
+ const getReadyState = () => connState.ws?.readyState ?? WS_CLOSED;
2904
+ connect();
2905
+ return { close, getReadyState, send, subscribe };
2906
+ };
2907
+ // src/ai/client/messageStore.ts
2908
+ var EMPTY_STATE = {
2909
+ activeConversationId: null,
2910
+ conversations: new Map,
2911
+ error: null,
2912
+ isStreaming: false
2913
+ };
2914
+ var initialActiveConversationId = null;
2915
+ var initialError = null;
2916
+ var freshState = () => ({
2917
+ activeConversationId: initialActiveConversationId,
2918
+ conversations: new Map,
2919
+ error: initialError,
2920
+ isStreaming: false
2921
+ });
2922
+ var findAssistantMessage = (conversation, messageId) => conversation.messages.find((msg) => msg.id === messageId && msg.role === "assistant");
2923
+ var getOrCreate = (state, conversationId) => {
2924
+ let conversation = state.conversations.get(conversationId);
2925
+ if (!conversation) {
2926
+ conversation = {
2927
+ createdAt: Date.now(),
2928
+ id: conversationId,
2929
+ messages: []
2930
+ };
2931
+ state.conversations.set(conversationId, conversation);
2932
+ }
2933
+ return conversation;
2934
+ };
2935
+ var handleSend = (state, action) => {
2936
+ const conversation = getOrCreate(state, action.conversationId);
2937
+ const message = {
2938
+ attachments: action.attachments,
2939
+ content: action.content,
2940
+ conversationId: action.conversationId,
2941
+ id: action.messageId,
2942
+ role: "user",
2943
+ timestamp: Date.now()
2944
+ };
2945
+ conversation.messages = [...conversation.messages, message];
2946
+ state.activeConversationId = action.conversationId;
2947
+ state.error = null;
2948
+ state.isStreaming = true;
2949
+ };
2950
+ var handleChunk = (state, action) => {
2951
+ const conversation = getOrCreate(state, action.conversationId);
2952
+ const existingIdx = conversation.messages.findIndex((msg) => msg.id === action.messageId && msg.role === "assistant");
2953
+ if (existingIdx >= 0) {
2954
+ const prevContent = conversation.messages[existingIdx]?.content ?? "";
2955
+ conversation.messages = conversation.messages.map((msg, idx) => idx === existingIdx ? { ...msg, content: prevContent + action.content } : msg);
2956
+ return;
2957
+ }
2958
+ const message = {
2959
+ content: action.content,
2960
+ conversationId: action.conversationId,
2961
+ id: action.messageId,
2962
+ isStreaming: true,
2963
+ role: "assistant",
2964
+ timestamp: Date.now()
2965
+ };
2966
+ conversation.messages = [...conversation.messages, message];
2967
+ };
2968
+ var handleThinking = (state, action) => {
2969
+ const conversation = getOrCreate(state, action.conversationId);
2970
+ const existingIdx = conversation.messages.findIndex((msg) => msg.id === action.messageId && msg.role === "assistant");
2971
+ if (existingIdx >= 0) {
2972
+ const prevThinking = conversation.messages[existingIdx]?.thinking ?? "";
2973
+ conversation.messages = conversation.messages.map((msg, idx) => idx === existingIdx ? { ...msg, thinking: prevThinking + action.content } : msg);
2974
+ return;
2975
+ }
2976
+ const message = {
2977
+ content: "",
2978
+ conversationId: action.conversationId,
2979
+ id: action.messageId,
2980
+ isStreaming: true,
2981
+ role: "assistant",
2982
+ thinking: action.content,
2983
+ timestamp: Date.now()
2984
+ };
2985
+ conversation.messages = [...conversation.messages, message];
2986
+ };
2987
+ var upsertToolCall = (message, toolCall) => {
2988
+ if (!message.toolCalls) {
2989
+ message.toolCalls = [toolCall];
2990
+ return;
2991
+ }
2992
+ const existingIdx = message.toolCalls.findIndex((existing) => existing.name === toolCall.name);
2993
+ if (existingIdx >= 0) {
2994
+ message.toolCalls[existingIdx] = toolCall;
2995
+ } else {
2996
+ message.toolCalls = [...message.toolCalls, toolCall];
2997
+ }
2998
+ };
2999
+ var getOrCreateAssistantMessage = (conversation, messageId, conversationId) => {
3000
+ const existing = findAssistantMessage(conversation, messageId);
3001
+ if (existing) {
3002
+ return existing;
3003
+ }
3004
+ const message = {
3005
+ content: "",
3006
+ conversationId,
3007
+ id: messageId,
3008
+ isStreaming: true,
3009
+ role: "assistant",
3010
+ timestamp: Date.now()
3011
+ };
3012
+ conversation.messages = [...conversation.messages, message];
3013
+ return message;
3014
+ };
3015
+ var handleRAGRetrieved = (state, action) => {
3016
+ const conversation = getOrCreate(state, action.conversationId);
3017
+ const message = getOrCreateAssistantMessage(conversation, action.messageId, action.conversationId);
3018
+ message.sources = action.sources;
3019
+ message.retrievalStartedAt = action.retrievalStartedAt ?? message.retrievalStartedAt;
3020
+ message.retrievedAt = action.retrievedAt;
3021
+ message.retrievalDurationMs = action.retrievalDurationMs;
3022
+ message.retrievalTrace = action.trace;
3023
+ conversation.messages = [...conversation.messages];
3024
+ };
3025
+ var handleRAGRetrieving = (state, action) => {
3026
+ const conversation = getOrCreate(state, action.conversationId);
3027
+ const message = getOrCreateAssistantMessage(conversation, action.messageId, action.conversationId);
3028
+ message.retrievalStartedAt = action.retrievalStartedAt;
3029
+ conversation.messages = [...conversation.messages];
3030
+ };
3031
+ var handleToolStatus = (state, action) => {
3032
+ const conversation = getOrCreate(state, action.conversationId);
3033
+ const message = getOrCreateAssistantMessage(conversation, action.messageId, action.conversationId);
3034
+ const toolCall = {
3035
+ id: action.messageId,
3036
+ input: action.input,
3037
+ name: action.name,
3038
+ result: action.status === "complete" ? action.result ?? "" : undefined
3039
+ };
3040
+ upsertToolCall(message, toolCall);
3041
+ conversation.messages = [...conversation.messages];
3042
+ };
3043
+ var markMessageComplete = (conversation, messageId, usage, durationMs, model, sources) => {
3044
+ conversation.messages = conversation.messages.map((msg) => msg.id === messageId && msg.role === "assistant" ? {
3045
+ ...msg,
3046
+ durationMs,
3047
+ isStreaming: false,
3048
+ model,
3049
+ sources: sources ?? msg.sources,
3050
+ usage
3051
+ } : msg);
3052
+ };
3053
+ var NOT_FOUND4 = -1;
3054
+ var findExistingImageById = (images, imageId) => {
3055
+ if (!imageId)
3056
+ return NOT_FOUND4;
3057
+ return images.findIndex((img) => img.imageId === imageId);
3058
+ };
3059
+ var findReplaceablePartialIndex = (images) => {
3060
+ const lastIdx = images.length - 1;
3061
+ if (lastIdx >= 0 && images[lastIdx]?.isPartial) {
3062
+ return lastIdx;
3063
+ }
3064
+ return NOT_FOUND4;
3065
+ };
3066
+ var upsertImage = (message, imageData) => {
3067
+ if (!message.images) {
3068
+ message.images = [imageData];
3069
+ return;
3070
+ }
3071
+ const existingIdx = findExistingImageById(message.images, imageData.imageId);
3072
+ if (existingIdx >= 0) {
3073
+ message.images[existingIdx] = imageData;
3074
+ return;
3075
+ }
3076
+ const replaceableIdx = findReplaceablePartialIndex(message.images);
3077
+ if (replaceableIdx >= 0) {
3078
+ message.images[replaceableIdx] = imageData;
3079
+ return;
3080
+ }
3081
+ message.images = [...message.images, imageData];
3082
+ };
3083
+ var handleImage = (state, action) => {
3084
+ const conversation = getOrCreate(state, action.conversationId);
3085
+ const message = getOrCreateAssistantMessage(conversation, action.messageId, action.conversationId);
3086
+ upsertImage(message, {
3087
+ data: action.data,
3088
+ format: action.format,
3089
+ imageId: action.imageId,
3090
+ isPartial: action.isPartial,
3091
+ revisedPrompt: action.revisedPrompt
3092
+ });
3093
+ conversation.messages = [...conversation.messages];
3094
+ };
3095
+ var handleComplete = (state, action) => {
3096
+ const conversation = state.conversations.get(action.conversationId);
3097
+ if (conversation) {
3098
+ markMessageComplete(conversation, action.messageId, action.usage, action.durationMs, action.model, action.sources);
3099
+ }
3100
+ state.isStreaming = false;
3101
+ };
3102
+ var markConversationStreamsComplete = (conversation) => {
3103
+ const streamingMessages = conversation.messages.filter((msg) => msg.isStreaming);
3104
+ if (streamingMessages.length === 0) {
3105
+ return;
3106
+ }
3107
+ for (const msg of streamingMessages) {
3108
+ msg.isStreaming = false;
3109
+ }
3110
+ conversation.messages = [...conversation.messages];
3111
+ };
3112
+ var markAllStreamsComplete = (state) => {
3113
+ for (const [, conversation] of state.conversations) {
3114
+ markConversationStreamsComplete(conversation);
3115
+ }
3116
+ };
3117
+ var handleBranch = (state, action) => {
3118
+ const source = state.conversations.get(action.oldConversationId);
3119
+ if (!source) {
3120
+ return;
3121
+ }
3122
+ const cutoffIndex = source.messages.findIndex((msg) => msg.id === action.fromMessageId);
3123
+ if (cutoffIndex < 0) {
3124
+ return;
3125
+ }
3126
+ const branchedMessages = source.messages.slice(0, cutoffIndex + 1).map((msg) => ({ ...msg, conversationId: action.newConversationId }));
3127
+ const newConversation = {
3128
+ createdAt: Date.now(),
3129
+ id: action.newConversationId,
3130
+ messages: branchedMessages
3131
+ };
3132
+ state.conversations.set(action.newConversationId, newConversation);
3133
+ state.activeConversationId = action.newConversationId;
3134
+ };
3135
+ var applyAction = (state, action) => {
3136
+ switch (action.type) {
3137
+ case "send":
3138
+ handleSend(state, action);
3139
+ break;
3140
+ case "chunk":
3141
+ handleChunk(state, action);
3142
+ break;
3143
+ case "thinking":
3144
+ handleThinking(state, action);
3145
+ break;
3146
+ case "tool_status":
3147
+ handleToolStatus(state, action);
3148
+ break;
3149
+ case "image":
3150
+ handleImage(state, action);
3151
+ break;
3152
+ case "complete":
3153
+ handleComplete(state, action);
3154
+ break;
3155
+ case "error":
3156
+ state.error = action.message;
3157
+ state.isStreaming = false;
3158
+ break;
3159
+ case "rag_retrieving":
3160
+ handleRAGRetrieving(state, action);
3161
+ break;
3162
+ case "rag_retrieved":
3163
+ handleRAGRetrieved(state, action);
3164
+ break;
3165
+ case "cancel":
3166
+ state.isStreaming = false;
3167
+ markAllStreamsComplete(state);
3168
+ break;
3169
+ case "branch":
3170
+ handleBranch(state, action);
3171
+ break;
3172
+ case "set_conversation":
3173
+ state.activeConversationId = action.conversationId;
3174
+ break;
3175
+ }
3176
+ };
3177
+ var createAIMessageStore = () => {
3178
+ let state = freshState();
3179
+ const subscribers = new Set;
3180
+ return {
3181
+ dispatch: (action) => {
3182
+ applyAction(state, action);
3183
+ state = { ...state, conversations: new Map(state.conversations) };
3184
+ subscribers.forEach((callback) => callback());
3185
+ },
3186
+ getServerSnapshot: () => EMPTY_STATE,
3187
+ getSnapshot: () => state,
3188
+ subscribe: (callback) => {
3189
+ subscribers.add(callback);
3190
+ return () => {
3191
+ subscribers.delete(callback);
3192
+ };
3193
+ }
3194
+ };
3195
+ };
3196
+
3197
+ // src/ai/client/createAIStream.ts
3198
+ var createAIStream = (path, conversationId) => {
3199
+ const connection = createAIConnection(path);
3200
+ const store = createAIMessageStore();
3201
+ const listeners = new Set;
3202
+ let currentError = null;
3203
+ let currentIsStreaming = false;
3204
+ let currentMessages = [];
3205
+ let activeConversationId = conversationId ?? null;
3206
+ const syncState = () => {
3207
+ const snapshot = store.getSnapshot();
3208
+ const convId = activeConversationId ?? snapshot.activeConversationId;
3209
+ const conversation = convId ? snapshot.conversations.get(convId) : undefined;
3210
+ activeConversationId = convId ?? snapshot.activeConversationId;
3211
+ currentError = snapshot.error;
3212
+ currentIsStreaming = snapshot.isStreaming;
3213
+ currentMessages = conversation?.messages ?? [];
3214
+ listeners.forEach((listener) => listener());
3215
+ };
3216
+ const unsubscribeStore = store.subscribe(syncState);
3217
+ const unsubscribeConnection = connection.subscribe((message) => {
3218
+ const action = serverMessageToAction(message);
3219
+ if (action) {
3220
+ store.dispatch(action);
3221
+ }
3222
+ });
3223
+ const branch = (messageId, content) => {
3224
+ if (activeConversationId) {
3225
+ connection.send({
3226
+ content,
3227
+ conversationId: activeConversationId,
3228
+ messageId,
3229
+ type: "branch"
3230
+ });
3231
+ }
3232
+ };
3233
+ const cancel = () => {
3234
+ if (activeConversationId) {
3235
+ store.dispatch({ type: "cancel" });
3236
+ connection.send({
3237
+ conversationId: activeConversationId,
3238
+ type: "cancel"
3239
+ });
3240
+ }
3241
+ };
3242
+ const destroy = () => {
3243
+ unsubscribeStore();
3244
+ unsubscribeConnection();
3245
+ connection.close();
3246
+ listeners.clear();
3247
+ };
3248
+ const send = (content, attachments) => {
3249
+ const convId = activeConversationId ?? generateId();
3250
+ const msgId = generateId();
3251
+ store.dispatch({
3252
+ attachments,
3253
+ content,
3254
+ conversationId: convId,
3255
+ messageId: msgId,
3256
+ type: "send"
3257
+ });
3258
+ connection.send({
3259
+ attachments,
3260
+ content,
3261
+ conversationId: convId,
3262
+ type: "message"
3263
+ });
3264
+ };
3265
+ const subscribe = (callback) => {
3266
+ listeners.add(callback);
3267
+ return () => {
3268
+ listeners.delete(callback);
3269
+ };
3270
+ };
3271
+ return {
3272
+ branch,
3273
+ cancel,
3274
+ destroy,
3275
+ send,
3276
+ subscribe,
3277
+ get error() {
3278
+ return currentError;
3279
+ },
3280
+ get isStreaming() {
3281
+ return currentIsStreaming;
3282
+ },
3283
+ get messages() {
3284
+ return currentMessages;
3285
+ }
3286
+ };
3287
+ };
3288
+ export {
3289
+ xai,
3290
+ streamAIToSSE,
3291
+ streamAI,
3292
+ serverMessageToAction,
3293
+ serializeAIMessage,
3294
+ resolveRenderers,
3295
+ parseAIMessage,
3296
+ openaiResponses,
3297
+ openaiCompatible,
3298
+ openai,
3299
+ ollama,
3300
+ moonshot,
3301
+ mistralai,
3302
+ meta,
3303
+ google,
3304
+ generateId,
3305
+ gemini,
3306
+ deepseek,
3307
+ createMemoryStore,
3308
+ createConversationManager,
3309
+ createAIStream,
3310
+ createAIConnection,
3311
+ anthropic,
3312
+ alibaba,
3313
+ aiChat
3314
+ };
3315
+
3316
+ //# debugId=165B0DAA4823A12464756E2164756E21
3317
+ //# sourceMappingURL=index.js.map