@absolutejs/ai 0.0.46 → 0.0.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/ai/client/index.js +173 -3
- package/dist/ai/client/index.js.map +8 -7
- package/dist/ai/index.js +281 -31
- package/dist/ai/index.js.map +9 -8
- package/dist/angular/ai/index.js +63 -2
- package/dist/angular/ai/index.js.map +6 -6
- package/dist/react/ai/index.js +62 -2
- package/dist/react/ai/index.js.map +6 -6
- package/dist/src/ai/client/actions.d.ts +113 -0
- package/dist/src/ai/client/index.d.ts +1 -0
- package/dist/src/ai/client/turnQueue.d.ts +40 -0
- package/dist/svelte/ai/index.js +62 -2
- package/dist/svelte/ai/index.js.map +6 -6
- package/dist/types/ai.d.ts +34 -1
- package/dist/vue/ai/index.js +62 -2
- package/dist/vue/ai/index.js.map +6 -6
- package/package.json +1 -1
package/dist/ai/index.js
CHANGED
|
@@ -2259,6 +2259,115 @@ var createMemoryStore = () => {
|
|
|
2259
2259
|
return { get, getOrCreate, list, remove, set };
|
|
2260
2260
|
};
|
|
2261
2261
|
|
|
2262
|
+
// src/ai/client/turnQueue.ts
|
|
2263
|
+
var DEFAULT_MAX_SIZE = 100;
|
|
2264
|
+
|
|
2265
|
+
class ConversationTurnQueueFullError extends Error {
|
|
2266
|
+
constructor(maxSize) {
|
|
2267
|
+
super(`Conversation turn queue is full (${maxSize} items)`);
|
|
2268
|
+
this.name = "ConversationTurnQueueFullError";
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
var createConversationTurnQueue = (options) => {
|
|
2272
|
+
const maxSize = options.maxSize ?? DEFAULT_MAX_SIZE;
|
|
2273
|
+
const items = [];
|
|
2274
|
+
const listeners = new Set;
|
|
2275
|
+
let activeAbort = null;
|
|
2276
|
+
let draining = false;
|
|
2277
|
+
const snapshot2 = () => ({
|
|
2278
|
+
items: items.map((item) => ({ ...item })),
|
|
2279
|
+
running: items.some(({ status }) => status === "running")
|
|
2280
|
+
});
|
|
2281
|
+
const notify = () => {
|
|
2282
|
+
const next = snapshot2();
|
|
2283
|
+
listeners.forEach((listener) => listener(next));
|
|
2284
|
+
};
|
|
2285
|
+
const drain = async () => {
|
|
2286
|
+
if (draining)
|
|
2287
|
+
return;
|
|
2288
|
+
const next = items.find(({ status }) => status === "queued");
|
|
2289
|
+
if (!next || items.some(({ status }) => status === "failed"))
|
|
2290
|
+
return;
|
|
2291
|
+
draining = true;
|
|
2292
|
+
next.status = "running";
|
|
2293
|
+
activeAbort = new AbortController;
|
|
2294
|
+
notify();
|
|
2295
|
+
try {
|
|
2296
|
+
await options.execute(next.input, {
|
|
2297
|
+
id: next.id,
|
|
2298
|
+
signal: activeAbort.signal
|
|
2299
|
+
});
|
|
2300
|
+
const index = items.findIndex(({ id }) => id === next.id);
|
|
2301
|
+
if (index >= 0)
|
|
2302
|
+
items.splice(index, 1);
|
|
2303
|
+
} catch (error) {
|
|
2304
|
+
if (activeAbort.signal.aborted) {
|
|
2305
|
+
const index = items.findIndex(({ id }) => id === next.id);
|
|
2306
|
+
if (index >= 0)
|
|
2307
|
+
items.splice(index, 1);
|
|
2308
|
+
} else {
|
|
2309
|
+
next.error = error;
|
|
2310
|
+
next.status = "failed";
|
|
2311
|
+
options.onError?.(error, { ...next });
|
|
2312
|
+
}
|
|
2313
|
+
} finally {
|
|
2314
|
+
activeAbort = null;
|
|
2315
|
+
draining = false;
|
|
2316
|
+
notify();
|
|
2317
|
+
drain();
|
|
2318
|
+
}
|
|
2319
|
+
};
|
|
2320
|
+
const enqueue = (input, id = crypto.randomUUID()) => {
|
|
2321
|
+
if (items.length >= maxSize) {
|
|
2322
|
+
throw new ConversationTurnQueueFullError(maxSize);
|
|
2323
|
+
}
|
|
2324
|
+
items.push({ id, input, status: "queued" });
|
|
2325
|
+
notify();
|
|
2326
|
+
drain();
|
|
2327
|
+
return id;
|
|
2328
|
+
};
|
|
2329
|
+
const remove = (id) => {
|
|
2330
|
+
const index = items.findIndex((item) => item.id === id && item.status !== "running");
|
|
2331
|
+
if (index < 0)
|
|
2332
|
+
return false;
|
|
2333
|
+
items.splice(index, 1);
|
|
2334
|
+
notify();
|
|
2335
|
+
drain();
|
|
2336
|
+
return true;
|
|
2337
|
+
};
|
|
2338
|
+
const retry = (id) => {
|
|
2339
|
+
const item = items.find((candidate) => candidate.id === id && candidate.status === "failed");
|
|
2340
|
+
if (!item)
|
|
2341
|
+
return false;
|
|
2342
|
+
delete item.error;
|
|
2343
|
+
item.status = "queued";
|
|
2344
|
+
notify();
|
|
2345
|
+
drain();
|
|
2346
|
+
return true;
|
|
2347
|
+
};
|
|
2348
|
+
const cancel = (input = {}) => {
|
|
2349
|
+
activeAbort?.abort();
|
|
2350
|
+
if (input.clearPending) {
|
|
2351
|
+
for (let index = items.length - 1;index >= 0; index--) {
|
|
2352
|
+
if (items[index]?.status !== "running")
|
|
2353
|
+
items.splice(index, 1);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
notify();
|
|
2357
|
+
};
|
|
2358
|
+
return {
|
|
2359
|
+
cancel,
|
|
2360
|
+
enqueue,
|
|
2361
|
+
getSnapshot: snapshot2,
|
|
2362
|
+
remove,
|
|
2363
|
+
retry,
|
|
2364
|
+
subscribe: (listener) => {
|
|
2365
|
+
listeners.add(listener);
|
|
2366
|
+
return () => listeners.delete(listener);
|
|
2367
|
+
}
|
|
2368
|
+
};
|
|
2369
|
+
};
|
|
2370
|
+
|
|
2262
2371
|
// types/typeGuards.ts
|
|
2263
2372
|
var isValidAIClientMessage = (data) => {
|
|
2264
2373
|
if (!data || typeof data !== "object") {
|
|
@@ -2295,6 +2404,12 @@ var isValidAIServerMessage = (data) => {
|
|
|
2295
2404
|
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;
|
|
2296
2405
|
case "complete":
|
|
2297
2406
|
return "messageId" in data && "conversationId" in data;
|
|
2407
|
+
case "turn_queued":
|
|
2408
|
+
return "conversationId" in data && typeof data.conversationId === "string" && "messageId" in data && typeof data.messageId === "string" && "position" in data && typeof data.position === "number";
|
|
2409
|
+
case "turn_started":
|
|
2410
|
+
return "conversationId" in data && typeof data.conversationId === "string" && "messageId" in data && typeof data.messageId === "string";
|
|
2411
|
+
case "branched":
|
|
2412
|
+
return "content" in data && typeof data.content === "string" && "fromMessageId" in data && typeof data.fromMessageId === "string" && "messageId" in data && typeof data.messageId === "string" && "newConversationId" in data && typeof data.newConversationId === "string" && "oldConversationId" in data && typeof data.oldConversationId === "string";
|
|
2298
2413
|
case "rag_retrieved":
|
|
2299
2414
|
return "conversationId" in data && "messageId" in data && "sources" in data && Array.isArray(data.sources);
|
|
2300
2415
|
case "error":
|
|
@@ -3210,14 +3325,20 @@ var aiChat = (config2) => {
|
|
|
3210
3325
|
const store = config2.store ?? createMemoryStore();
|
|
3211
3326
|
const parseProvider = config2.parseProvider ?? defaultParseProvider;
|
|
3212
3327
|
const abortControllers = new Map;
|
|
3328
|
+
const turnQueues = new Map;
|
|
3329
|
+
const sendServerEvent = (ws, event) => {
|
|
3330
|
+
if (ws.readyState === 1)
|
|
3331
|
+
ws.send(JSON.stringify(event));
|
|
3332
|
+
};
|
|
3213
3333
|
const handleCancel = (conversationId) => {
|
|
3334
|
+
turnQueues.get(conversationId)?.cancel({ clearPending: true });
|
|
3214
3335
|
const controller = abortControllers.get(conversationId);
|
|
3215
3336
|
if (controller) {
|
|
3216
3337
|
controller.abort();
|
|
3217
3338
|
abortControllers.delete(conversationId);
|
|
3218
3339
|
}
|
|
3219
3340
|
};
|
|
3220
|
-
const handleBranch = async (ws, messageId, conversationId) => {
|
|
3341
|
+
const handleBranch = async (ws, messageId, conversationId, content) => {
|
|
3221
3342
|
const source = await store.get(conversationId);
|
|
3222
3343
|
if (!source) {
|
|
3223
3344
|
return;
|
|
@@ -3225,55 +3346,123 @@ var aiChat = (config2) => {
|
|
|
3225
3346
|
const newConv = branchConversation(source, messageId);
|
|
3226
3347
|
if (newConv) {
|
|
3227
3348
|
await store.set(newConv.id, newConv);
|
|
3228
|
-
|
|
3349
|
+
const clientMessageId = generateId();
|
|
3350
|
+
sendServerEvent(ws, {
|
|
3351
|
+
content,
|
|
3352
|
+
fromMessageId: messageId,
|
|
3353
|
+
messageId: clientMessageId,
|
|
3354
|
+
newConversationId: newConv.id,
|
|
3355
|
+
oldConversationId: conversationId,
|
|
3356
|
+
type: "branched"
|
|
3357
|
+
});
|
|
3358
|
+
enqueueUserMessage({
|
|
3359
|
+
clientMessageId,
|
|
3360
|
+
content,
|
|
3361
|
+
conversationId: newConv.id,
|
|
3362
|
+
ws
|
|
3363
|
+
});
|
|
3229
3364
|
}
|
|
3230
3365
|
};
|
|
3231
|
-
const handleUserMessage = async (ws, rawContent,
|
|
3232
|
-
const
|
|
3233
|
-
const messageId = generateId();
|
|
3366
|
+
const handleUserMessage = async (ws, rawContent, conversationId, clientMessageId, attachments, queueSignal) => {
|
|
3367
|
+
const assistantMessageId = generateId();
|
|
3234
3368
|
const parsed = parseProvider(rawContent);
|
|
3235
3369
|
const { content, providerName } = parsed;
|
|
3236
3370
|
const conversation = await store.getOrCreate(conversationId);
|
|
3237
3371
|
const history = getHistory(conversation);
|
|
3238
3372
|
const controller = new AbortController;
|
|
3239
3373
|
abortControllers.set(conversationId, controller);
|
|
3374
|
+
const abortFromQueue = () => controller.abort();
|
|
3375
|
+
queueSignal?.addEventListener("abort", abortFromQueue, { once: true });
|
|
3240
3376
|
appendMessage(conversation, {
|
|
3241
3377
|
attachments,
|
|
3242
3378
|
content,
|
|
3243
3379
|
conversationId,
|
|
3244
|
-
id:
|
|
3380
|
+
id: clientMessageId,
|
|
3245
3381
|
role: "user",
|
|
3246
3382
|
timestamp: Date.now()
|
|
3247
3383
|
});
|
|
3248
3384
|
await store.set(conversationId, conversation);
|
|
3249
3385
|
const model = resolveModel(config2, parsed);
|
|
3250
3386
|
const userMessage = buildUserMessage(content, attachments);
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3387
|
+
try {
|
|
3388
|
+
await streamAI(ws, conversationId, assistantMessageId, {
|
|
3389
|
+
maxTurns: config2.maxTurns,
|
|
3390
|
+
messages: [...history, userMessage],
|
|
3391
|
+
model,
|
|
3392
|
+
provider: config2.provider(providerName),
|
|
3393
|
+
signal: controller.signal,
|
|
3394
|
+
systemPrompt: config2.systemPrompt,
|
|
3395
|
+
reasoning: resolveReasoning(config2, providerName, model),
|
|
3396
|
+
tools: resolveTools(config2, providerName, model),
|
|
3397
|
+
onComplete: async (fullResponse, usage) => {
|
|
3398
|
+
const conv = await store.get(conversationId);
|
|
3399
|
+
if (conv) {
|
|
3400
|
+
appendMessage(conv, {
|
|
3401
|
+
content: fullResponse,
|
|
3402
|
+
conversationId,
|
|
3403
|
+
id: assistantMessageId,
|
|
3404
|
+
role: "assistant",
|
|
3405
|
+
timestamp: Date.now()
|
|
3406
|
+
});
|
|
3407
|
+
await store.set(conversationId, conv);
|
|
3408
|
+
}
|
|
3409
|
+
config2.onComplete?.(conversationId, fullResponse, usage);
|
|
3271
3410
|
}
|
|
3411
|
+
});
|
|
3412
|
+
} finally {
|
|
3413
|
+
queueSignal?.removeEventListener("abort", abortFromQueue);
|
|
3414
|
+
if (abortControllers.get(conversationId) === controller) {
|
|
3272
3415
|
abortControllers.delete(conversationId);
|
|
3273
|
-
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
};
|
|
3419
|
+
const queueForConversation = (conversationId) => {
|
|
3420
|
+
const existing = turnQueues.get(conversationId);
|
|
3421
|
+
if (existing)
|
|
3422
|
+
return existing;
|
|
3423
|
+
const queue = createConversationTurnQueue({
|
|
3424
|
+
execute: async (turn, { signal }) => {
|
|
3425
|
+
sendServerEvent(turn.ws, {
|
|
3426
|
+
conversationId: turn.conversationId,
|
|
3427
|
+
messageId: turn.clientMessageId,
|
|
3428
|
+
type: "turn_started"
|
|
3429
|
+
});
|
|
3430
|
+
await handleUserMessage(turn.ws, turn.content, turn.conversationId, turn.clientMessageId, turn.attachments, signal);
|
|
3431
|
+
},
|
|
3432
|
+
onError: (error, turn) => {
|
|
3433
|
+
sendServerEvent(turn.input.ws, {
|
|
3434
|
+
message: error instanceof Error ? error.message : "AI turn failed",
|
|
3435
|
+
type: "error"
|
|
3436
|
+
});
|
|
3437
|
+
}
|
|
3438
|
+
});
|
|
3439
|
+
turnQueues.set(conversationId, queue);
|
|
3440
|
+
let unsubscribe = () => {
|
|
3441
|
+
return;
|
|
3442
|
+
};
|
|
3443
|
+
unsubscribe = queue.subscribe(({ items }) => {
|
|
3444
|
+
if (items.length === 0 && turnQueues.get(conversationId) === queue) {
|
|
3445
|
+
turnQueues.delete(conversationId);
|
|
3446
|
+
unsubscribe();
|
|
3274
3447
|
}
|
|
3275
3448
|
});
|
|
3449
|
+
return queue;
|
|
3276
3450
|
};
|
|
3451
|
+
function enqueueUserMessage(turn) {
|
|
3452
|
+
const queue = queueForConversation(turn.conversationId);
|
|
3453
|
+
const wasBusy = queue.getSnapshot().items.length > 0;
|
|
3454
|
+
queue.enqueue(turn, turn.clientMessageId);
|
|
3455
|
+
if (wasBusy) {
|
|
3456
|
+
const queued = queue.getSnapshot().items.filter(({ status }) => status === "queued");
|
|
3457
|
+
const position = queued.findIndex(({ id }) => id === turn.clientMessageId) + 1;
|
|
3458
|
+
sendServerEvent(turn.ws, {
|
|
3459
|
+
conversationId: turn.conversationId,
|
|
3460
|
+
messageId: turn.clientMessageId,
|
|
3461
|
+
position,
|
|
3462
|
+
type: "turn_queued"
|
|
3463
|
+
});
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3277
3466
|
const htmxRoutes = () => {
|
|
3278
3467
|
if (!config2.htmx) {
|
|
3279
3468
|
return new Elysia;
|
|
@@ -3366,11 +3555,18 @@ var aiChat = (config2) => {
|
|
|
3366
3555
|
return;
|
|
3367
3556
|
}
|
|
3368
3557
|
if (msg.type === "branch") {
|
|
3369
|
-
await handleBranch(ws, msg.messageId, msg.conversationId);
|
|
3558
|
+
await handleBranch(ws, msg.messageId, msg.conversationId, msg.content);
|
|
3370
3559
|
return;
|
|
3371
3560
|
}
|
|
3372
3561
|
if (msg.type === "message") {
|
|
3373
|
-
|
|
3562
|
+
const conversationId = msg.conversationId ?? generateId();
|
|
3563
|
+
enqueueUserMessage({
|
|
3564
|
+
attachments: msg.attachments,
|
|
3565
|
+
clientMessageId: msg.messageId ?? generateId(),
|
|
3566
|
+
content: msg.content,
|
|
3567
|
+
conversationId,
|
|
3568
|
+
ws
|
|
3569
|
+
});
|
|
3374
3570
|
}
|
|
3375
3571
|
}
|
|
3376
3572
|
}).get(`${path}/conversations`, () => store.list()).get(`${path}/conversations/:id`, async ({ params }) => {
|
|
@@ -5202,6 +5398,28 @@ var serverMessageToAction = (message) => {
|
|
|
5202
5398
|
type: "complete",
|
|
5203
5399
|
usage: message.usage
|
|
5204
5400
|
};
|
|
5401
|
+
case "turn_queued":
|
|
5402
|
+
return {
|
|
5403
|
+
conversationId: message.conversationId,
|
|
5404
|
+
messageId: message.messageId,
|
|
5405
|
+
position: message.position,
|
|
5406
|
+
type: "turn_queued"
|
|
5407
|
+
};
|
|
5408
|
+
case "turn_started":
|
|
5409
|
+
return {
|
|
5410
|
+
conversationId: message.conversationId,
|
|
5411
|
+
messageId: message.messageId,
|
|
5412
|
+
type: "turn_started"
|
|
5413
|
+
};
|
|
5414
|
+
case "branched":
|
|
5415
|
+
return {
|
|
5416
|
+
content: message.content,
|
|
5417
|
+
fromMessageId: message.fromMessageId,
|
|
5418
|
+
messageId: message.messageId,
|
|
5419
|
+
newConversationId: message.newConversationId,
|
|
5420
|
+
oldConversationId: message.oldConversationId,
|
|
5421
|
+
type: "branch"
|
|
5422
|
+
};
|
|
5205
5423
|
case "rag_retrieving":
|
|
5206
5424
|
return {
|
|
5207
5425
|
conversationId: message.conversationId,
|
|
@@ -5403,6 +5621,7 @@ var handleSend = (state, action) => {
|
|
|
5403
5621
|
content: action.content,
|
|
5404
5622
|
conversationId: action.conversationId,
|
|
5405
5623
|
id: action.messageId,
|
|
5624
|
+
isQueued: false,
|
|
5406
5625
|
role: "user",
|
|
5407
5626
|
timestamp: Date.now()
|
|
5408
5627
|
};
|
|
@@ -5411,6 +5630,19 @@ var handleSend = (state, action) => {
|
|
|
5411
5630
|
state.error = null;
|
|
5412
5631
|
state.isStreaming = true;
|
|
5413
5632
|
};
|
|
5633
|
+
var updateUserQueueState = (state, conversationId, messageId, isQueued) => {
|
|
5634
|
+
const conversation = state.conversations.get(conversationId);
|
|
5635
|
+
if (!conversation)
|
|
5636
|
+
return;
|
|
5637
|
+
conversation.messages = conversation.messages.map((message) => message.id === messageId && message.role === "user" ? { ...message, isQueued } : message);
|
|
5638
|
+
};
|
|
5639
|
+
var handleTurnQueued = (state, action) => {
|
|
5640
|
+
updateUserQueueState(state, action.conversationId, action.messageId, true);
|
|
5641
|
+
};
|
|
5642
|
+
var handleTurnStarted = (state, action) => {
|
|
5643
|
+
updateUserQueueState(state, action.conversationId, action.messageId, false);
|
|
5644
|
+
state.isStreaming = true;
|
|
5645
|
+
};
|
|
5414
5646
|
var handleChunk = (state, action) => {
|
|
5415
5647
|
const conversation = getOrCreate(state, action.conversationId);
|
|
5416
5648
|
const existingIdx = conversation.messages.findIndex((msg) => msg.id === action.messageId && msg.role === "assistant");
|
|
@@ -5591,10 +5823,21 @@ var handleBranch = (state, action) => {
|
|
|
5591
5823
|
const newConversation = {
|
|
5592
5824
|
createdAt: Date.now(),
|
|
5593
5825
|
id: action.newConversationId,
|
|
5594
|
-
messages:
|
|
5826
|
+
messages: [
|
|
5827
|
+
...branchedMessages,
|
|
5828
|
+
{
|
|
5829
|
+
content: action.content,
|
|
5830
|
+
conversationId: action.newConversationId,
|
|
5831
|
+
id: action.messageId,
|
|
5832
|
+
isQueued: false,
|
|
5833
|
+
role: "user",
|
|
5834
|
+
timestamp: Date.now()
|
|
5835
|
+
}
|
|
5836
|
+
]
|
|
5595
5837
|
};
|
|
5596
5838
|
state.conversations.set(action.newConversationId, newConversation);
|
|
5597
5839
|
state.activeConversationId = action.newConversationId;
|
|
5840
|
+
state.isStreaming = true;
|
|
5598
5841
|
};
|
|
5599
5842
|
var applyAction = (state, action) => {
|
|
5600
5843
|
switch (action.type) {
|
|
@@ -5616,6 +5859,12 @@ var applyAction = (state, action) => {
|
|
|
5616
5859
|
case "complete":
|
|
5617
5860
|
handleComplete(state, action);
|
|
5618
5861
|
break;
|
|
5862
|
+
case "turn_queued":
|
|
5863
|
+
handleTurnQueued(state, action);
|
|
5864
|
+
break;
|
|
5865
|
+
case "turn_started":
|
|
5866
|
+
handleTurnStarted(state, action);
|
|
5867
|
+
break;
|
|
5619
5868
|
case "error":
|
|
5620
5869
|
state.error = action.message;
|
|
5621
5870
|
state.isStreaming = false;
|
|
@@ -5723,6 +5972,7 @@ var createAIStream = (path, conversationId) => {
|
|
|
5723
5972
|
attachments,
|
|
5724
5973
|
content,
|
|
5725
5974
|
conversationId: convId,
|
|
5975
|
+
messageId: msgId,
|
|
5726
5976
|
type: "message"
|
|
5727
5977
|
});
|
|
5728
5978
|
};
|
|
@@ -5999,5 +6249,5 @@ export {
|
|
|
5999
6249
|
BUILTIN_UI_CARDS
|
|
6000
6250
|
};
|
|
6001
6251
|
|
|
6002
|
-
//# debugId=
|
|
6252
|
+
//# debugId=79EC11089E8CB24964756E2164756E21
|
|
6003
6253
|
//# sourceMappingURL=index.js.map
|