@absolutejs/absolute 0.19.0-beta.241 → 0.19.0-beta.243

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.
@@ -259,7 +259,8 @@
259
259
  "mcp__playwright__browser_take_screenshot",
260
260
  "Bash(xargs -r kill -9)",
261
261
  "mcp__playwright__browser_type",
262
- "Bash(ollama list:*)"
262
+ "Bash(ollama list:*)",
263
+ "WebFetch(domain:platform.openai.com)"
263
264
  ]
264
265
  }
265
266
  }
package/dist/ai/index.js CHANGED
@@ -134,6 +134,308 @@ var isValidAIClientMessage = (data) => {
134
134
  }
135
135
  };
136
136
 
137
+ // src/ai/providers/openai.ts
138
+ var DEFAULT_BASE_URL = "https://api.openai.com";
139
+ var SSE_DATA_PREFIX_LENGTH = 6;
140
+ var DONE_SENTINEL = "[DONE]";
141
+ var NOT_FOUND = -1;
142
+ var isRecord = (value) => typeof value === "object" && value !== null;
143
+ var isRecordArray = (value) => Array.isArray(value) && value.length > 0 && isRecord(value[0]);
144
+ var hasArrayContent = (msg) => typeof msg.content !== "string" && Array.isArray(msg.content);
145
+ var buildToolMessages = (blocks) => {
146
+ const toolUseBlocks = blocks.filter((block) => block.type === "tool_use");
147
+ const toolResultBlocks = blocks.filter((block) => block.type === "tool_result");
148
+ const messages = [];
149
+ if (toolUseBlocks.length > 0) {
150
+ messages.push({
151
+ content: null,
152
+ role: "assistant",
153
+ tool_calls: toolUseBlocks.map((block) => ({
154
+ function: {
155
+ arguments: typeof block.input === "string" ? block.input : JSON.stringify(block.input),
156
+ name: block.name
157
+ },
158
+ id: block.id,
159
+ type: "function"
160
+ }))
161
+ });
162
+ }
163
+ for (const result of toolResultBlocks) {
164
+ messages.push({
165
+ content: typeof result.content === "string" ? result.content : "",
166
+ role: "tool",
167
+ tool_call_id: result.tool_use_id
168
+ });
169
+ }
170
+ return messages;
171
+ };
172
+ var processMessageAtIndex = (result, msg, idx) => {
173
+ if (!hasArrayContent(msg)) {
174
+ return;
175
+ }
176
+ const hasToolBlocks = msg.content.some((block) => block.type === "tool_use" || block.type === "tool_result");
177
+ if (!hasToolBlocks) {
178
+ return;
179
+ }
180
+ const toolMessages = buildToolMessages(msg.content);
181
+ result.splice(idx, 1, ...toolMessages);
182
+ };
183
+ var convertSingleMessage = (result, msg, idx) => {
184
+ if (!msg) {
185
+ return;
186
+ }
187
+ processMessageAtIndex(result, msg, idx);
188
+ };
189
+ var convertToolResultMessages = (messages, params) => {
190
+ const result = [...messages];
191
+ for (let idx = 0;idx < params.messages.length; idx++) {
192
+ convertSingleMessage(result, params.messages[idx], idx);
193
+ }
194
+ return result;
195
+ };
196
+ var mapToolDefinitions = (tools) => tools.map((tool) => ({
197
+ function: {
198
+ description: tool.description,
199
+ name: tool.name,
200
+ parameters: tool.input_schema
201
+ },
202
+ type: "function"
203
+ }));
204
+ var buildRequestBody = (params) => {
205
+ const messages = convertToolResultMessages(params.messages.map((msg) => ({
206
+ content: typeof msg.content === "string" ? msg.content : null,
207
+ role: msg.role
208
+ })), params);
209
+ const body = {
210
+ messages,
211
+ model: params.model,
212
+ stream: true,
213
+ stream_options: { include_usage: true }
214
+ };
215
+ if (params.tools && params.tools.length > 0) {
216
+ body.tools = mapToolDefinitions(params.tools);
217
+ }
218
+ return body;
219
+ };
220
+ var parseToolInput = (rawArguments) => {
221
+ try {
222
+ return JSON.parse(rawArguments);
223
+ } catch {
224
+ return rawArguments;
225
+ }
226
+ };
227
+ var flushPendingToolCalls = function* (pendingToolCalls) {
228
+ for (const [, tool] of pendingToolCalls) {
229
+ const input = parseToolInput(tool.arguments);
230
+ yield {
231
+ id: tool.id,
232
+ input,
233
+ name: tool.name,
234
+ type: "tool_use"
235
+ };
236
+ }
237
+ pendingToolCalls.clear();
238
+ };
239
+ var extractUsage = (parsedUsage) => ({
240
+ inputTokens: parsedUsage.prompt_tokens ?? 0,
241
+ outputTokens: parsedUsage.completion_tokens ?? 0
242
+ });
243
+ var resolveToolCallIndex = (toolCall) => {
244
+ const raw = typeof toolCall.index === "number" ? toolCall.index : NOT_FOUND;
245
+ return raw < 0 ? undefined : raw;
246
+ };
247
+ var initPendingToolCall = (toolCall, func, index, pendingToolCalls) => {
248
+ if (pendingToolCalls.has(index)) {
249
+ return;
250
+ }
251
+ const toolId = typeof toolCall.id === "string" ? toolCall.id : "";
252
+ const toolName = func && typeof func.name === "string" ? func.name : "";
253
+ pendingToolCalls.set(index, {
254
+ arguments: "",
255
+ id: toolId,
256
+ name: toolName
257
+ });
258
+ };
259
+ var updatePendingToolCall = (toolCall, func, pending) => {
260
+ if (typeof toolCall.id === "string") {
261
+ pending.id = toolCall.id;
262
+ }
263
+ if (func && typeof func.name === "string") {
264
+ pending.name = func.name;
265
+ }
266
+ if (func && typeof func.arguments === "string") {
267
+ pending.arguments += func.arguments;
268
+ }
269
+ };
270
+ var processToolCallDelta = (toolCall, pendingToolCalls) => {
271
+ const index = resolveToolCallIndex(toolCall);
272
+ if (index === undefined) {
273
+ return;
274
+ }
275
+ const func = isRecord(toolCall.function) ? toolCall.function : null;
276
+ initPendingToolCall(toolCall, func, index, pendingToolCalls);
277
+ const pending = pendingToolCalls.get(index);
278
+ if (!pending) {
279
+ return;
280
+ }
281
+ updatePendingToolCall(toolCall, func, pending);
282
+ };
283
+ var processToolCallDeltas = (toolCalls, pendingToolCalls) => {
284
+ for (const toolCall of toolCalls) {
285
+ processToolCallDelta(toolCall, pendingToolCalls);
286
+ }
287
+ };
288
+ var processDelta = function* (delta, pendingToolCalls) {
289
+ if (typeof delta.content === "string") {
290
+ yield { content: delta.content, type: "text" };
291
+ }
292
+ if (isRecordArray(delta.tool_calls)) {
293
+ processToolCallDeltas(delta.tool_calls, pendingToolCalls);
294
+ }
295
+ };
296
+ var processChoice = function* (choice, pendingToolCalls) {
297
+ const delta = isRecord(choice.delta) ? choice.delta : null;
298
+ if (delta) {
299
+ yield* processDelta(delta, pendingToolCalls);
300
+ }
301
+ if (choice.finish_reason === "tool_calls") {
302
+ yield* flushPendingToolCalls(pendingToolCalls);
303
+ }
304
+ };
305
+ var narrowUsageRecord = (parsed) => {
306
+ if (!isRecord(parsed.usage)) {
307
+ return;
308
+ }
309
+ const { usage } = parsed;
310
+ const promptTokens = typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : 0;
311
+ const completionTokens = typeof usage.completion_tokens === "number" ? usage.completion_tokens : 0;
312
+ return extractUsage({
313
+ completion_tokens: completionTokens,
314
+ prompt_tokens: promptTokens
315
+ });
316
+ };
317
+ var processSSELine = function* (line, pendingToolCalls, currentUsage) {
318
+ const trimmed = line.trim();
319
+ if (!trimmed || !trimmed.startsWith("data: ")) {
320
+ return;
321
+ }
322
+ const data = trimmed.slice(SSE_DATA_PREFIX_LENGTH);
323
+ if (data === DONE_SENTINEL) {
324
+ yield* flushPendingToolCalls(pendingToolCalls);
325
+ yield { type: "done", usage: currentUsage };
326
+ return;
327
+ }
328
+ let parsed;
329
+ try {
330
+ parsed = JSON.parse(data);
331
+ } catch {
332
+ return;
333
+ }
334
+ const usageUpdate = narrowUsageRecord(parsed);
335
+ if (usageUpdate) {
336
+ yield { type: "usage_update", usage: usageUpdate };
337
+ }
338
+ const { choices } = parsed;
339
+ if (!isRecordArray(choices)) {
340
+ return;
341
+ }
342
+ const [firstChoice] = choices;
343
+ if (!firstChoice) {
344
+ return;
345
+ }
346
+ yield* processChoice(firstChoice, pendingToolCalls);
347
+ };
348
+ var isUsageUpdate = (chunk) => chunk.type === "usage_update";
349
+ var collectYieldableChunks = (line, pendingToolCalls, usageRef) => {
350
+ const allChunks = Array.from(processSSELine(line, pendingToolCalls, usageRef.current));
351
+ const usageChunks = allChunks.filter(isUsageUpdate);
352
+ const lastUsage = usageChunks.at(NOT_FOUND);
353
+ if (lastUsage) {
354
+ usageRef.current = lastUsage.usage;
355
+ }
356
+ return allChunks.filter((chunk) => !isUsageUpdate(chunk));
357
+ };
358
+ var processSSELines = function* (lines, pendingToolCalls, usageRef) {
359
+ for (const line of lines) {
360
+ yield* collectYieldableChunks(line, pendingToolCalls, usageRef);
361
+ }
362
+ };
363
+ var processStreamValue = (value, decoder, state) => {
364
+ state.buffer += decoder.decode(value, { stream: true });
365
+ const lines = state.buffer.split(`
366
+ `);
367
+ state.buffer = lines.pop() ?? "";
368
+ return lines;
369
+ };
370
+ var drainReader = async function* (reader, decoder, state, signal) {
371
+ for (let result = await reader.read();!result.done && !signal?.aborted; result = await reader.read()) {
372
+ const lines = processStreamValue(result.value, decoder, state);
373
+ yield* processSSELines(lines, state.pendingToolCalls, state.usageRef);
374
+ }
375
+ };
376
+ var parseSSEStream = async function* (body, signal) {
377
+ const reader = body.getReader();
378
+ const decoder = new TextDecoder;
379
+ const state = {
380
+ buffer: "",
381
+ pendingToolCalls: new Map,
382
+ usageRef: { current: undefined }
383
+ };
384
+ try {
385
+ yield* drainReader(reader, decoder, state, signal);
386
+ yield { type: "done", usage: state.usageRef.current };
387
+ } finally {
388
+ reader.releaseLock();
389
+ }
390
+ };
391
+ var fetchOpenAIStream = async function* (baseUrl, apiKey, body, signal) {
392
+ const response = await fetch(`${baseUrl}/v1/chat/completions`, {
393
+ body: JSON.stringify(body),
394
+ headers: {
395
+ Authorization: `Bearer ${apiKey}`,
396
+ "Content-Type": "application/json"
397
+ },
398
+ method: "POST",
399
+ signal
400
+ });
401
+ if (!response.ok) {
402
+ const errorText = await response.text();
403
+ throw new Error(`OpenAI API error ${response.status}: ${errorText}`);
404
+ }
405
+ if (!response.body) {
406
+ throw new Error("OpenAI API returned no response body");
407
+ }
408
+ yield* parseSSEStream(response.body, signal);
409
+ };
410
+ var openai = (config) => {
411
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
412
+ return {
413
+ stream: (params) => {
414
+ const body = buildRequestBody(params);
415
+ return fetchOpenAIStream(baseUrl, config.apiKey, body, params.signal);
416
+ }
417
+ };
418
+ };
419
+
420
+ // src/ai/providers/openaiCompatible.ts
421
+ var openaiCompatible = (config) => openai({ apiKey: config.apiKey, baseUrl: config.baseUrl });
422
+ var google = (config) => openaiCompatible({
423
+ apiKey: config.apiKey,
424
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai"
425
+ });
426
+ var xai = (config) => openaiCompatible({
427
+ apiKey: config.apiKey,
428
+ baseUrl: "https://api.x.ai"
429
+ });
430
+ var deepseek = (config) => openaiCompatible({
431
+ apiKey: config.apiKey,
432
+ baseUrl: "https://api.deepseek.com"
433
+ });
434
+ var mistralai = (config) => openaiCompatible({
435
+ apiKey: config.apiKey,
436
+ baseUrl: "https://api.mistral.ai"
437
+ });
438
+
137
439
  // src/plugins/aiChat.ts
138
440
  import { Elysia } from "elysia";
139
441
 
@@ -171,14 +473,14 @@ var parseAIMessage = (raw) => {
171
473
  var serializeAIMessage = (msg) => JSON.stringify(msg);
172
474
 
173
475
  // src/ai/conversationManager.ts
174
- var NOT_FOUND = -1;
476
+ var NOT_FOUND2 = -1;
175
477
  var createConversationManager = () => {
176
478
  const conversations = new Map;
177
479
  const getOrCreate = (conversationId) => {
178
480
  const id = conversationId ?? generateId();
179
481
  let conversation = conversations.get(id);
180
482
  if (!conversation) {
181
- conversation = { id, messages: [] };
483
+ conversation = { createdAt: Date.now(), id, messages: [] };
182
484
  conversations.set(id, conversation);
183
485
  }
184
486
  return conversation;
@@ -186,6 +488,10 @@ var createConversationManager = () => {
186
488
  const appendMessage = (conversationId, message) => {
187
489
  const conversation = getOrCreate(conversationId);
188
490
  conversation.messages.push(message);
491
+ conversation.lastMessageAt = Date.now();
492
+ if (!conversation.title && message.role === "user") {
493
+ conversation.title = message.content.slice(0, 80);
494
+ }
189
495
  };
190
496
  const branch = (fromMessageId, sourceConversationId) => {
191
497
  const source = conversations.get(sourceConversationId);
@@ -193,12 +499,13 @@ var createConversationManager = () => {
193
499
  return null;
194
500
  }
195
501
  const cutoffIndex = source.messages.findIndex((msg) => msg.id === fromMessageId);
196
- if (cutoffIndex === NOT_FOUND) {
502
+ if (cutoffIndex === NOT_FOUND2) {
197
503
  return null;
198
504
  }
199
505
  const newId = generateId();
200
506
  const branchedMessages = source.messages.slice(0, cutoffIndex + 1).map((msg) => ({ ...msg, conversationId: newId }));
201
507
  const newConversation = {
508
+ createdAt: Date.now(),
202
509
  id: newId,
203
510
  messages: branchedMessages
204
511
  };
@@ -232,6 +539,20 @@ var createConversationManager = () => {
232
539
  };
233
540
  const get = (conversationId) => conversations.get(conversationId);
234
541
  const remove = (conversationId) => conversations.delete(conversationId);
542
+ const list = () => Array.from(conversations.values()).map((conv) => ({
543
+ createdAt: conv.createdAt,
544
+ id: conv.id,
545
+ lastMessageAt: conv.lastMessageAt,
546
+ messageCount: conv.messages.length,
547
+ title: conv.title ?? "Untitled"
548
+ })).sort((first, second) => (second.lastMessageAt ?? second.createdAt) - (first.lastMessageAt ?? first.createdAt));
549
+ const getMessages = (conversationId) => {
550
+ const conversation = conversations.get(conversationId);
551
+ if (!conversation) {
552
+ return [];
553
+ }
554
+ return conversation.messages;
555
+ };
235
556
  return {
236
557
  abort,
237
558
  appendMessage,
@@ -239,7 +560,9 @@ var createConversationManager = () => {
239
560
  get,
240
561
  getAbortController,
241
562
  getHistory,
563
+ getMessages,
242
564
  getOrCreate,
565
+ list,
243
566
  remove
244
567
  };
245
568
  };
@@ -423,10 +746,11 @@ var executeToolLoop = async (socket, options, messages, toolUseId, toolName, too
423
746
  usage: state.currentUsage
424
747
  };
425
748
  };
426
- var sendComplete = async (socket, messageId, conversationId, usage, durationMs) => sendMessage(socket, {
749
+ var sendComplete = async (socket, messageId, conversationId, usage, durationMs, model) => sendMessage(socket, {
427
750
  conversationId,
428
751
  durationMs,
429
752
  messageId,
753
+ model,
430
754
  type: "complete",
431
755
  usage
432
756
  });
@@ -448,7 +772,7 @@ var handleTextChunk = async (chunk, options, socket, messageId, conversationId)
448
772
  };
449
773
  var handleToolUseChunk = async (socket, options, messages, chunkId, chunkName, chunkInput, messageId, conversationId, signal, fullResponse, startTime) => {
450
774
  const toolResult = await executeToolLoop(socket, options, messages, chunkId, chunkName, chunkInput, messageId, conversationId, signal, fullResponse, INITIAL_TURN);
451
- await sendComplete(socket, messageId, conversationId, toolResult.usage, Date.now() - startTime);
775
+ await sendComplete(socket, messageId, conversationId, toolResult.usage, Date.now() - startTime, options.model);
452
776
  options.onComplete?.(toolResult.fullResponse, toolResult.usage);
453
777
  return toolResult;
454
778
  };
@@ -470,7 +794,7 @@ var processStream = async (socket, options, messages, messageId, conversationId,
470
794
  });
471
795
  const result = await consumeStream(stream, options, socket, messages, messageId, conversationId, signal, startTime);
472
796
  if (!result.earlyReturn) {
473
- await sendComplete(socket, messageId, conversationId, result.usage, Date.now() - startTime);
797
+ await sendComplete(socket, messageId, conversationId, result.usage, Date.now() - startTime, options.model);
474
798
  options.onComplete?.(result.fullResponse, result.usage);
475
799
  }
476
800
  };
@@ -622,16 +946,34 @@ var aiChat = (config) => {
622
946
  await handleUserMessage(ws, msg.content, msg.conversationId);
623
947
  }
624
948
  }
949
+ }).get(`${path}/conversations`, () => conversations.list()).get(`${path}/conversations/:id`, ({ params }) => {
950
+ const conv = conversations.get(params.id);
951
+ if (!conv) {
952
+ return new Response("Not found", { status: 404 });
953
+ }
954
+ return {
955
+ id: conv.id,
956
+ messages: conv.messages,
957
+ title: conv.title ?? "Untitled"
958
+ };
959
+ }).delete(`${path}/conversations/:id`, ({ params }) => {
960
+ conversations.remove(params.id);
961
+ return { ok: true };
625
962
  });
626
963
  };
627
964
  export {
965
+ xai,
628
966
  streamAI,
629
967
  serializeAIMessage,
630
968
  parseAIMessage,
969
+ openaiCompatible,
970
+ mistralai,
971
+ google,
631
972
  generateId,
973
+ deepseek,
632
974
  createConversationManager,
633
975
  aiChat
634
976
  };
635
977
 
636
- //# debugId=2FEA80C34C79993464756E2164756E21
978
+ //# debugId=1D1368F3FA8E162364756E2164756E21
637
979
  //# sourceMappingURL=index.js.map