@dsh-enhanced/assistant-delivery 0.1.3 → 0.1.4
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 +2 -1
- package/lib/agent-runtime.d.ts +21 -8
- package/lib/agent-runtime.d.ts.map +1 -1
- package/lib/agent-runtime.js +282 -16
- package/lib/agent-runtime.js.map +1 -1
- package/lib/service.d.ts +8 -22
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +158 -11
- package/lib/service.js.map +1 -1
- package/lib/store.d.ts +27 -0
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +70 -4
- package/lib/store.js.map +1 -1
- package/lib/types.d.ts +35 -5
- package/lib/types.d.ts.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +3 -3
package/lib/service.js
CHANGED
|
@@ -51,6 +51,8 @@ function pickerIncludesRoute(picker, route) {
|
|
|
51
51
|
const model = picker.models.find(candidate => candidate.provider === route.provider && candidate.id === route.model);
|
|
52
52
|
return model !== undefined && (route.reasoningEffort === undefined || model.effortIds.includes(route.reasoningEffort));
|
|
53
53
|
}
|
|
54
|
+
const MODEL_SELECTION_WAIT_POLL_MS = 500;
|
|
55
|
+
const MODEL_SELECTION_WAIT_TIMEOUT_MS = 120_000;
|
|
54
56
|
function isModelSelectionPayload(value) {
|
|
55
57
|
if (value === null || typeof value !== 'object')
|
|
56
58
|
return false;
|
|
@@ -58,6 +60,7 @@ function isModelSelectionPayload(value) {
|
|
|
58
60
|
const principal = payload.principal;
|
|
59
61
|
return typeof payload.callbackEventId === 'string'
|
|
60
62
|
&& typeof payload.callbackChatId === 'string'
|
|
63
|
+
&& typeof payload.cardMessageId === 'string'
|
|
61
64
|
&& typeof payload.bindingId === 'string'
|
|
62
65
|
&& principal !== undefined
|
|
63
66
|
&& typeof principal.channel === 'string'
|
|
@@ -173,6 +176,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
173
176
|
conversationTransitions = new Map();
|
|
174
177
|
agentApprovalBindings = new WeakMap();
|
|
175
178
|
toolApprovalControllers = new Set();
|
|
179
|
+
modelSelectionWaiters = new Map();
|
|
176
180
|
modelSelectionFlight;
|
|
177
181
|
modelSelectionRetryTimer;
|
|
178
182
|
runtime;
|
|
@@ -232,6 +236,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
232
236
|
modelPickerTtlMs: config.modelPickerTtlMs,
|
|
233
237
|
permissionPickerTtlMs: config.permissionPickerTtlMs,
|
|
234
238
|
getModelSelection: conversation => this.deliveryStore.getModelSelection(conversation),
|
|
239
|
+
clearStaleModelReasoningEffort: (conversation, expected) => this.deliveryStore.clearStaleModelReasoningEffort({ conversation, expected }),
|
|
235
240
|
imageMaterializer,
|
|
236
241
|
isInboundAuthorized: (binding, envelope) => this.isInboundAuthorized(binding, envelope),
|
|
237
242
|
isPermissionController: (binding, envelope) => this.isPermissionController(binding, envelope),
|
|
@@ -277,6 +282,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
277
282
|
this.start();
|
|
278
283
|
ctx.effect(() => async () => {
|
|
279
284
|
this.active = false;
|
|
285
|
+
this.cancelModelSelectionWaiters();
|
|
280
286
|
for (const controller of this.toolApprovalControllers) {
|
|
281
287
|
controller.abort(new Error('assistant-delivery is stopping'));
|
|
282
288
|
}
|
|
@@ -439,13 +445,18 @@ export class AssistantDeliveryService extends Service {
|
|
|
439
445
|
|| JSON.stringify(binding.principal) !== JSON.stringify(current.principal)) {
|
|
440
446
|
throw new AssistantDeliveryError('missing-binding', 'model callback principal or chat does not own the active binding');
|
|
441
447
|
}
|
|
442
|
-
const
|
|
448
|
+
const record = this.deliveryStore.getModelPickerRecord(input.operationId, input.bindingId);
|
|
443
449
|
const expected = {
|
|
444
450
|
revision: input.expectedRevision,
|
|
445
451
|
provider: input.modelProvider,
|
|
446
452
|
model: input.model,
|
|
447
453
|
...(input.reasoningEffort === undefined ? {} : { reasoningEffort: input.reasoningEffort }),
|
|
448
454
|
};
|
|
455
|
+
if (record === undefined || !['accepted', 'delivered', 'read'].includes(record.status)
|
|
456
|
+
|| record.providerMessageId !== input.cardMessageId) {
|
|
457
|
+
throw new AssistantDeliveryError('missing-binding', 'model picker confirmation is unavailable or invalid');
|
|
458
|
+
}
|
|
459
|
+
const picker = record.intent.modelPicker;
|
|
449
460
|
if (picker === undefined || !pickerIncludesRoute(picker, expected)) {
|
|
450
461
|
throw new AssistantDeliveryError('missing-binding', 'model picker confirmation is unavailable or invalid');
|
|
451
462
|
}
|
|
@@ -457,6 +468,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
457
468
|
const payload = {
|
|
458
469
|
callbackEventId: input.callbackEventId,
|
|
459
470
|
callbackChatId: input.callbackChatId,
|
|
471
|
+
cardMessageId: input.cardMessageId,
|
|
460
472
|
bindingId: input.bindingId,
|
|
461
473
|
principal: current.principal,
|
|
462
474
|
provider: input.provider,
|
|
@@ -477,6 +489,112 @@ export class AssistantDeliveryService extends Service {
|
|
|
477
489
|
void this.drainModelSelectionSettlements();
|
|
478
490
|
return { status: 'pending' };
|
|
479
491
|
}
|
|
492
|
+
async awaitModelSelection(input, signal) {
|
|
493
|
+
this.assertActive();
|
|
494
|
+
const expected = {
|
|
495
|
+
revision: input.expectedRevision,
|
|
496
|
+
provider: input.modelProvider,
|
|
497
|
+
model: input.model,
|
|
498
|
+
...(input.reasoningEffort === undefined ? {} : { reasoningEffort: input.reasoningEffort }),
|
|
499
|
+
};
|
|
500
|
+
const payload = {
|
|
501
|
+
callbackEventId: input.callbackEventId,
|
|
502
|
+
callbackChatId: input.callbackChatId,
|
|
503
|
+
cardMessageId: input.cardMessageId,
|
|
504
|
+
bindingId: input.bindingId,
|
|
505
|
+
principal: input.principal,
|
|
506
|
+
provider: input.provider,
|
|
507
|
+
modelProvider: input.modelProvider,
|
|
508
|
+
model: input.model,
|
|
509
|
+
reasoningEffort: input.reasoningEffort ?? null,
|
|
510
|
+
expectedRevision: input.expectedRevision,
|
|
511
|
+
};
|
|
512
|
+
const read = () => this.deliveryStore.getModelSelectionSettlement({
|
|
513
|
+
operationId: input.operationId, bindingId: input.bindingId, expected, payload,
|
|
514
|
+
});
|
|
515
|
+
const current = read();
|
|
516
|
+
if (current?.result !== undefined)
|
|
517
|
+
return current.result;
|
|
518
|
+
if (current === undefined) {
|
|
519
|
+
throw new AssistantDeliveryError('missing-binding', 'model selection settlement does not exist');
|
|
520
|
+
}
|
|
521
|
+
if (signal.aborted)
|
|
522
|
+
return undefined;
|
|
523
|
+
return await new Promise((resolve, reject) => {
|
|
524
|
+
let settled = false;
|
|
525
|
+
const finish = (result) => {
|
|
526
|
+
if (settled)
|
|
527
|
+
return;
|
|
528
|
+
settled = true;
|
|
529
|
+
if (waiter.pollTimer !== undefined)
|
|
530
|
+
clearTimeout(waiter.pollTimer);
|
|
531
|
+
if (waiter.deadlineTimer !== undefined)
|
|
532
|
+
clearTimeout(waiter.deadlineTimer);
|
|
533
|
+
signal.removeEventListener('abort', waiter.abort);
|
|
534
|
+
const waiters = this.modelSelectionWaiters.get(input.operationId);
|
|
535
|
+
waiters?.delete(waiter);
|
|
536
|
+
if (waiters?.size === 0)
|
|
537
|
+
this.modelSelectionWaiters.delete(input.operationId);
|
|
538
|
+
resolve(result);
|
|
539
|
+
};
|
|
540
|
+
const waiter = { abort: () => finish(undefined), resolve: finish };
|
|
541
|
+
const fail = (error) => {
|
|
542
|
+
if (settled)
|
|
543
|
+
return;
|
|
544
|
+
settled = true;
|
|
545
|
+
if (waiter.pollTimer !== undefined)
|
|
546
|
+
clearTimeout(waiter.pollTimer);
|
|
547
|
+
if (waiter.deadlineTimer !== undefined)
|
|
548
|
+
clearTimeout(waiter.deadlineTimer);
|
|
549
|
+
signal.removeEventListener('abort', waiter.abort);
|
|
550
|
+
const currentWaiters = this.modelSelectionWaiters.get(input.operationId);
|
|
551
|
+
currentWaiters?.delete(waiter);
|
|
552
|
+
if (currentWaiters?.size === 0)
|
|
553
|
+
this.modelSelectionWaiters.delete(input.operationId);
|
|
554
|
+
reject(error);
|
|
555
|
+
};
|
|
556
|
+
const poll = () => {
|
|
557
|
+
if (settled)
|
|
558
|
+
return;
|
|
559
|
+
if (signal.aborted || !this.active) {
|
|
560
|
+
finish(undefined);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
try {
|
|
564
|
+
const latest = read();
|
|
565
|
+
if (latest?.result !== undefined) {
|
|
566
|
+
finish(latest.result);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
waiter.pollTimer = setTimeout(poll, MODEL_SELECTION_WAIT_POLL_MS);
|
|
570
|
+
waiter.pollTimer.unref?.();
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
fail(error);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
const waiters = this.modelSelectionWaiters.get(input.operationId) ?? new Set();
|
|
577
|
+
waiters.add(waiter);
|
|
578
|
+
this.modelSelectionWaiters.set(input.operationId, waiters);
|
|
579
|
+
signal.addEventListener('abort', waiter.abort, { once: true });
|
|
580
|
+
waiter.deadlineTimer = setTimeout(() => finish(undefined), MODEL_SELECTION_WAIT_TIMEOUT_MS);
|
|
581
|
+
waiter.deadlineTimer.unref?.();
|
|
582
|
+
try {
|
|
583
|
+
const raced = read();
|
|
584
|
+
if (raced?.result !== undefined)
|
|
585
|
+
finish(raced.result);
|
|
586
|
+
else if (signal.aborted || !this.active)
|
|
587
|
+
finish(undefined);
|
|
588
|
+
else {
|
|
589
|
+
waiter.pollTimer = setTimeout(poll, MODEL_SELECTION_WAIT_POLL_MS);
|
|
590
|
+
waiter.pollTimer.unref?.();
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
catch (error) {
|
|
594
|
+
fail(error);
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
}
|
|
480
598
|
advanceModelPickerForCallback(input) {
|
|
481
599
|
this.assertActive();
|
|
482
600
|
const binding = this.deliveryStore.getBinding(input.bindingId);
|
|
@@ -486,7 +604,12 @@ export class AssistantDeliveryService extends Service {
|
|
|
486
604
|
|| JSON.stringify(binding.principal) !== JSON.stringify(current.principal)) {
|
|
487
605
|
throw new AssistantDeliveryError('missing-binding', 'model callback principal or chat does not own the active binding');
|
|
488
606
|
}
|
|
489
|
-
const
|
|
607
|
+
const record = this.deliveryStore.getModelPickerRecord(input.operationId, input.bindingId);
|
|
608
|
+
if (record === undefined || !['accepted', 'delivered', 'read'].includes(record.status)
|
|
609
|
+
|| record.providerMessageId !== input.cardMessageId) {
|
|
610
|
+
throw new AssistantDeliveryError('missing-binding', 'model picker catalog or route is unavailable');
|
|
611
|
+
}
|
|
612
|
+
const picker = record.intent.modelPicker;
|
|
490
613
|
if (picker === undefined || Date.now() >= picker.expiresAt
|
|
491
614
|
|| !pickerIncludesRoute(picker, input.expected) || !pickerIncludesRoute(picker, input.next)) {
|
|
492
615
|
throw new AssistantDeliveryError('missing-binding', 'model picker catalog or route is unavailable');
|
|
@@ -507,7 +630,11 @@ export class AssistantDeliveryService extends Service {
|
|
|
507
630
|
|| JSON.stringify(binding.principal) !== JSON.stringify(current.principal)) {
|
|
508
631
|
throw new AssistantDeliveryError('missing-binding', 'model callback principal or chat does not own the active binding');
|
|
509
632
|
}
|
|
510
|
-
const
|
|
633
|
+
const record = this.deliveryStore.getModelPickerRecord(input.operationId, input.bindingId);
|
|
634
|
+
if (record === undefined || !['accepted', 'delivered', 'read'].includes(record.status)
|
|
635
|
+
|| record.providerMessageId !== input.cardMessageId)
|
|
636
|
+
return undefined;
|
|
637
|
+
const picker = record.intent.modelPicker;
|
|
511
638
|
return picker === undefined || Date.now() >= picker.expiresAt ? undefined : picker;
|
|
512
639
|
}
|
|
513
640
|
async settlePermissionSelection(input) {
|
|
@@ -1470,8 +1597,22 @@ export class AssistantDeliveryService extends Service {
|
|
|
1470
1597
|
clearTimeout(this.modelSelectionRetryTimer);
|
|
1471
1598
|
this.timer = undefined;
|
|
1472
1599
|
this.modelSelectionRetryTimer = undefined;
|
|
1600
|
+
this.cancelModelSelectionWaiters();
|
|
1473
1601
|
await Promise.all([this.inbound.stop(), this.outbound.stop(), this.modelSelectionFlight]);
|
|
1474
1602
|
}
|
|
1603
|
+
resolveModelSelectionWaiters(operationId, result) {
|
|
1604
|
+
const waiters = this.modelSelectionWaiters.get(operationId);
|
|
1605
|
+
if (waiters === undefined)
|
|
1606
|
+
return;
|
|
1607
|
+
for (const waiter of waiters)
|
|
1608
|
+
waiter.resolve(result);
|
|
1609
|
+
}
|
|
1610
|
+
cancelModelSelectionWaiters() {
|
|
1611
|
+
for (const waiters of this.modelSelectionWaiters.values()) {
|
|
1612
|
+
for (const waiter of waiters)
|
|
1613
|
+
waiter.resolve(undefined);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1475
1616
|
drainApprovalDispatches() {
|
|
1476
1617
|
const cursor = this.deliveryStore.getApprovalDispatchCursor();
|
|
1477
1618
|
const dispatches = this.policy.listPendingApprovalDispatches(100, cursor.after);
|
|
@@ -1553,13 +1694,17 @@ export class AssistantDeliveryService extends Service {
|
|
|
1553
1694
|
return;
|
|
1554
1695
|
}
|
|
1555
1696
|
await Promise.allSettled(claims.map(async (claim) => {
|
|
1556
|
-
const finishWithoutReply = (result) =>
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1697
|
+
const finishWithoutReply = (result) => {
|
|
1698
|
+
const completed = this.deliveryStore.completeModelSelectionSettlement({
|
|
1699
|
+
operationId: claim.operationId,
|
|
1700
|
+
payloadHash: claim.payloadHash,
|
|
1701
|
+
result,
|
|
1702
|
+
ownerId: this.ownerId,
|
|
1703
|
+
fencingToken: claim.fencingToken,
|
|
1704
|
+
});
|
|
1705
|
+
this.resolveModelSelectionWaiters(claim.operationId, completed);
|
|
1706
|
+
return completed;
|
|
1707
|
+
};
|
|
1563
1708
|
const payload = claim.payload;
|
|
1564
1709
|
if (!isModelSelectionPayload(payload) || payload.bindingId !== claim.bindingId) {
|
|
1565
1710
|
finishWithoutReply({ status: 'rejected', reason: 'authorization-revoked' });
|
|
@@ -1589,7 +1734,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
1589
1734
|
format: 'plain',
|
|
1590
1735
|
});
|
|
1591
1736
|
const superseded = { status: 'rejected', reason: 'selection-superseded' };
|
|
1592
|
-
|
|
1737
|
+
const completed = this.deliveryStore.completeModelSelectionSettlement({
|
|
1593
1738
|
operationId: claim.operationId,
|
|
1594
1739
|
payloadHash: claim.payloadHash,
|
|
1595
1740
|
result,
|
|
@@ -1602,6 +1747,8 @@ export class AssistantDeliveryService extends Service {
|
|
|
1602
1747
|
ownerId: this.ownerId,
|
|
1603
1748
|
fencingToken: claim.fencingToken,
|
|
1604
1749
|
});
|
|
1750
|
+
this.resolveModelSelectionWaiters(claim.operationId, completed);
|
|
1751
|
+
return completed;
|
|
1605
1752
|
};
|
|
1606
1753
|
const initialBinding = this.deliveryStore.getBinding(claim.bindingId);
|
|
1607
1754
|
if (!ownsBinding(initialBinding) || !authorized(initialBinding)) {
|