@dsh-enhanced/assistant-delivery 0.1.3 → 0.1.5
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 +3 -4
- package/cordis.patch.yml +0 -2
- package/lib/agent-runtime.d.ts +21 -10
- package/lib/agent-runtime.d.ts.map +1 -1
- package/lib/agent-runtime.js +303 -46
- package/lib/agent-runtime.js.map +1 -1
- package/lib/service.d.ts +8 -31
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +158 -15
- 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
|
@@ -37,8 +37,6 @@ const configSchema = Schema.object({
|
|
|
37
37
|
policyRef: Schema.string().min(1).default('owner-dm'),
|
|
38
38
|
agentProvider: Schema.string().min(1).default('deepseek-official'),
|
|
39
39
|
agentModel: Schema.string().min(1).default('deepseek-v4-flash'),
|
|
40
|
-
toolCapableProviders: Schema.array(Schema.string().pattern(/^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/u)).default(['deepseek-official']),
|
|
41
|
-
unknownRouteToolCalls: Schema.union(['allow', 'deny']).default('allow'),
|
|
42
40
|
agentMaxOutputTokens: Schema.number().step(1).min(1).default(8_192),
|
|
43
41
|
modelPickerTtlMs: Schema.number().step(1).min(60_000).max(86_400_000).default(900_000),
|
|
44
42
|
permissionPickerTtlMs: Schema.number().step(1).min(60_000).max(86_400_000).default(900_000),
|
|
@@ -51,6 +49,8 @@ function pickerIncludesRoute(picker, route) {
|
|
|
51
49
|
const model = picker.models.find(candidate => candidate.provider === route.provider && candidate.id === route.model);
|
|
52
50
|
return model !== undefined && (route.reasoningEffort === undefined || model.effortIds.includes(route.reasoningEffort));
|
|
53
51
|
}
|
|
52
|
+
const MODEL_SELECTION_WAIT_POLL_MS = 500;
|
|
53
|
+
const MODEL_SELECTION_WAIT_TIMEOUT_MS = 120_000;
|
|
54
54
|
function isModelSelectionPayload(value) {
|
|
55
55
|
if (value === null || typeof value !== 'object')
|
|
56
56
|
return false;
|
|
@@ -58,6 +58,7 @@ function isModelSelectionPayload(value) {
|
|
|
58
58
|
const principal = payload.principal;
|
|
59
59
|
return typeof payload.callbackEventId === 'string'
|
|
60
60
|
&& typeof payload.callbackChatId === 'string'
|
|
61
|
+
&& typeof payload.cardMessageId === 'string'
|
|
61
62
|
&& typeof payload.bindingId === 'string'
|
|
62
63
|
&& principal !== undefined
|
|
63
64
|
&& typeof principal.channel === 'string'
|
|
@@ -173,6 +174,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
173
174
|
conversationTransitions = new Map();
|
|
174
175
|
agentApprovalBindings = new WeakMap();
|
|
175
176
|
toolApprovalControllers = new Set();
|
|
177
|
+
modelSelectionWaiters = new Map();
|
|
176
178
|
modelSelectionFlight;
|
|
177
179
|
modelSelectionRetryTimer;
|
|
178
180
|
runtime;
|
|
@@ -227,11 +229,10 @@ export class AssistantDeliveryService extends Service {
|
|
|
227
229
|
workspace: config.defaultWorkspace, agentPreset: config.defaultAgentPreset, policyRef: config.policyRef,
|
|
228
230
|
getAgentPresets: () => runtimeCtx.get('agentPresets'),
|
|
229
231
|
provider: config.agentProvider, model: config.agentModel, maxOutputTokens: config.agentMaxOutputTokens,
|
|
230
|
-
toolCapableProviders: new Set(config.toolCapableProviders),
|
|
231
|
-
unknownRouteToolCalls: config.unknownRouteToolCalls ?? 'allow',
|
|
232
232
|
modelPickerTtlMs: config.modelPickerTtlMs,
|
|
233
233
|
permissionPickerTtlMs: config.permissionPickerTtlMs,
|
|
234
234
|
getModelSelection: conversation => this.deliveryStore.getModelSelection(conversation),
|
|
235
|
+
clearStaleModelReasoningEffort: (conversation, expected) => this.deliveryStore.clearStaleModelReasoningEffort({ conversation, expected }),
|
|
235
236
|
imageMaterializer,
|
|
236
237
|
isInboundAuthorized: (binding, envelope) => this.isInboundAuthorized(binding, envelope),
|
|
237
238
|
isPermissionController: (binding, envelope) => this.isPermissionController(binding, envelope),
|
|
@@ -277,6 +278,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
277
278
|
this.start();
|
|
278
279
|
ctx.effect(() => async () => {
|
|
279
280
|
this.active = false;
|
|
281
|
+
this.cancelModelSelectionWaiters();
|
|
280
282
|
for (const controller of this.toolApprovalControllers) {
|
|
281
283
|
controller.abort(new Error('assistant-delivery is stopping'));
|
|
282
284
|
}
|
|
@@ -439,13 +441,18 @@ export class AssistantDeliveryService extends Service {
|
|
|
439
441
|
|| JSON.stringify(binding.principal) !== JSON.stringify(current.principal)) {
|
|
440
442
|
throw new AssistantDeliveryError('missing-binding', 'model callback principal or chat does not own the active binding');
|
|
441
443
|
}
|
|
442
|
-
const
|
|
444
|
+
const record = this.deliveryStore.getModelPickerRecord(input.operationId, input.bindingId);
|
|
443
445
|
const expected = {
|
|
444
446
|
revision: input.expectedRevision,
|
|
445
447
|
provider: input.modelProvider,
|
|
446
448
|
model: input.model,
|
|
447
449
|
...(input.reasoningEffort === undefined ? {} : { reasoningEffort: input.reasoningEffort }),
|
|
448
450
|
};
|
|
451
|
+
if (record === undefined || !['accepted', 'delivered', 'read'].includes(record.status)
|
|
452
|
+
|| record.providerMessageId !== input.cardMessageId) {
|
|
453
|
+
throw new AssistantDeliveryError('missing-binding', 'model picker confirmation is unavailable or invalid');
|
|
454
|
+
}
|
|
455
|
+
const picker = record.intent.modelPicker;
|
|
449
456
|
if (picker === undefined || !pickerIncludesRoute(picker, expected)) {
|
|
450
457
|
throw new AssistantDeliveryError('missing-binding', 'model picker confirmation is unavailable or invalid');
|
|
451
458
|
}
|
|
@@ -457,6 +464,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
457
464
|
const payload = {
|
|
458
465
|
callbackEventId: input.callbackEventId,
|
|
459
466
|
callbackChatId: input.callbackChatId,
|
|
467
|
+
cardMessageId: input.cardMessageId,
|
|
460
468
|
bindingId: input.bindingId,
|
|
461
469
|
principal: current.principal,
|
|
462
470
|
provider: input.provider,
|
|
@@ -477,6 +485,112 @@ export class AssistantDeliveryService extends Service {
|
|
|
477
485
|
void this.drainModelSelectionSettlements();
|
|
478
486
|
return { status: 'pending' };
|
|
479
487
|
}
|
|
488
|
+
async awaitModelSelection(input, signal) {
|
|
489
|
+
this.assertActive();
|
|
490
|
+
const expected = {
|
|
491
|
+
revision: input.expectedRevision,
|
|
492
|
+
provider: input.modelProvider,
|
|
493
|
+
model: input.model,
|
|
494
|
+
...(input.reasoningEffort === undefined ? {} : { reasoningEffort: input.reasoningEffort }),
|
|
495
|
+
};
|
|
496
|
+
const payload = {
|
|
497
|
+
callbackEventId: input.callbackEventId,
|
|
498
|
+
callbackChatId: input.callbackChatId,
|
|
499
|
+
cardMessageId: input.cardMessageId,
|
|
500
|
+
bindingId: input.bindingId,
|
|
501
|
+
principal: input.principal,
|
|
502
|
+
provider: input.provider,
|
|
503
|
+
modelProvider: input.modelProvider,
|
|
504
|
+
model: input.model,
|
|
505
|
+
reasoningEffort: input.reasoningEffort ?? null,
|
|
506
|
+
expectedRevision: input.expectedRevision,
|
|
507
|
+
};
|
|
508
|
+
const read = () => this.deliveryStore.getModelSelectionSettlement({
|
|
509
|
+
operationId: input.operationId, bindingId: input.bindingId, expected, payload,
|
|
510
|
+
});
|
|
511
|
+
const current = read();
|
|
512
|
+
if (current?.result !== undefined)
|
|
513
|
+
return current.result;
|
|
514
|
+
if (current === undefined) {
|
|
515
|
+
throw new AssistantDeliveryError('missing-binding', 'model selection settlement does not exist');
|
|
516
|
+
}
|
|
517
|
+
if (signal.aborted)
|
|
518
|
+
return undefined;
|
|
519
|
+
return await new Promise((resolve, reject) => {
|
|
520
|
+
let settled = false;
|
|
521
|
+
const finish = (result) => {
|
|
522
|
+
if (settled)
|
|
523
|
+
return;
|
|
524
|
+
settled = true;
|
|
525
|
+
if (waiter.pollTimer !== undefined)
|
|
526
|
+
clearTimeout(waiter.pollTimer);
|
|
527
|
+
if (waiter.deadlineTimer !== undefined)
|
|
528
|
+
clearTimeout(waiter.deadlineTimer);
|
|
529
|
+
signal.removeEventListener('abort', waiter.abort);
|
|
530
|
+
const waiters = this.modelSelectionWaiters.get(input.operationId);
|
|
531
|
+
waiters?.delete(waiter);
|
|
532
|
+
if (waiters?.size === 0)
|
|
533
|
+
this.modelSelectionWaiters.delete(input.operationId);
|
|
534
|
+
resolve(result);
|
|
535
|
+
};
|
|
536
|
+
const waiter = { abort: () => finish(undefined), resolve: finish };
|
|
537
|
+
const fail = (error) => {
|
|
538
|
+
if (settled)
|
|
539
|
+
return;
|
|
540
|
+
settled = true;
|
|
541
|
+
if (waiter.pollTimer !== undefined)
|
|
542
|
+
clearTimeout(waiter.pollTimer);
|
|
543
|
+
if (waiter.deadlineTimer !== undefined)
|
|
544
|
+
clearTimeout(waiter.deadlineTimer);
|
|
545
|
+
signal.removeEventListener('abort', waiter.abort);
|
|
546
|
+
const currentWaiters = this.modelSelectionWaiters.get(input.operationId);
|
|
547
|
+
currentWaiters?.delete(waiter);
|
|
548
|
+
if (currentWaiters?.size === 0)
|
|
549
|
+
this.modelSelectionWaiters.delete(input.operationId);
|
|
550
|
+
reject(error);
|
|
551
|
+
};
|
|
552
|
+
const poll = () => {
|
|
553
|
+
if (settled)
|
|
554
|
+
return;
|
|
555
|
+
if (signal.aborted || !this.active) {
|
|
556
|
+
finish(undefined);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
try {
|
|
560
|
+
const latest = read();
|
|
561
|
+
if (latest?.result !== undefined) {
|
|
562
|
+
finish(latest.result);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
waiter.pollTimer = setTimeout(poll, MODEL_SELECTION_WAIT_POLL_MS);
|
|
566
|
+
waiter.pollTimer.unref?.();
|
|
567
|
+
}
|
|
568
|
+
catch (error) {
|
|
569
|
+
fail(error);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
const waiters = this.modelSelectionWaiters.get(input.operationId) ?? new Set();
|
|
573
|
+
waiters.add(waiter);
|
|
574
|
+
this.modelSelectionWaiters.set(input.operationId, waiters);
|
|
575
|
+
signal.addEventListener('abort', waiter.abort, { once: true });
|
|
576
|
+
waiter.deadlineTimer = setTimeout(() => finish(undefined), MODEL_SELECTION_WAIT_TIMEOUT_MS);
|
|
577
|
+
waiter.deadlineTimer.unref?.();
|
|
578
|
+
try {
|
|
579
|
+
const raced = read();
|
|
580
|
+
if (raced?.result !== undefined)
|
|
581
|
+
finish(raced.result);
|
|
582
|
+
else if (signal.aborted || !this.active)
|
|
583
|
+
finish(undefined);
|
|
584
|
+
else {
|
|
585
|
+
waiter.pollTimer = setTimeout(poll, MODEL_SELECTION_WAIT_POLL_MS);
|
|
586
|
+
waiter.pollTimer.unref?.();
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
fail(error);
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
}
|
|
480
594
|
advanceModelPickerForCallback(input) {
|
|
481
595
|
this.assertActive();
|
|
482
596
|
const binding = this.deliveryStore.getBinding(input.bindingId);
|
|
@@ -486,7 +600,12 @@ export class AssistantDeliveryService extends Service {
|
|
|
486
600
|
|| JSON.stringify(binding.principal) !== JSON.stringify(current.principal)) {
|
|
487
601
|
throw new AssistantDeliveryError('missing-binding', 'model callback principal or chat does not own the active binding');
|
|
488
602
|
}
|
|
489
|
-
const
|
|
603
|
+
const record = this.deliveryStore.getModelPickerRecord(input.operationId, input.bindingId);
|
|
604
|
+
if (record === undefined || !['accepted', 'delivered', 'read'].includes(record.status)
|
|
605
|
+
|| record.providerMessageId !== input.cardMessageId) {
|
|
606
|
+
throw new AssistantDeliveryError('missing-binding', 'model picker catalog or route is unavailable');
|
|
607
|
+
}
|
|
608
|
+
const picker = record.intent.modelPicker;
|
|
490
609
|
if (picker === undefined || Date.now() >= picker.expiresAt
|
|
491
610
|
|| !pickerIncludesRoute(picker, input.expected) || !pickerIncludesRoute(picker, input.next)) {
|
|
492
611
|
throw new AssistantDeliveryError('missing-binding', 'model picker catalog or route is unavailable');
|
|
@@ -507,7 +626,11 @@ export class AssistantDeliveryService extends Service {
|
|
|
507
626
|
|| JSON.stringify(binding.principal) !== JSON.stringify(current.principal)) {
|
|
508
627
|
throw new AssistantDeliveryError('missing-binding', 'model callback principal or chat does not own the active binding');
|
|
509
628
|
}
|
|
510
|
-
const
|
|
629
|
+
const record = this.deliveryStore.getModelPickerRecord(input.operationId, input.bindingId);
|
|
630
|
+
if (record === undefined || !['accepted', 'delivered', 'read'].includes(record.status)
|
|
631
|
+
|| record.providerMessageId !== input.cardMessageId)
|
|
632
|
+
return undefined;
|
|
633
|
+
const picker = record.intent.modelPicker;
|
|
511
634
|
return picker === undefined || Date.now() >= picker.expiresAt ? undefined : picker;
|
|
512
635
|
}
|
|
513
636
|
async settlePermissionSelection(input) {
|
|
@@ -1470,8 +1593,22 @@ export class AssistantDeliveryService extends Service {
|
|
|
1470
1593
|
clearTimeout(this.modelSelectionRetryTimer);
|
|
1471
1594
|
this.timer = undefined;
|
|
1472
1595
|
this.modelSelectionRetryTimer = undefined;
|
|
1596
|
+
this.cancelModelSelectionWaiters();
|
|
1473
1597
|
await Promise.all([this.inbound.stop(), this.outbound.stop(), this.modelSelectionFlight]);
|
|
1474
1598
|
}
|
|
1599
|
+
resolveModelSelectionWaiters(operationId, result) {
|
|
1600
|
+
const waiters = this.modelSelectionWaiters.get(operationId);
|
|
1601
|
+
if (waiters === undefined)
|
|
1602
|
+
return;
|
|
1603
|
+
for (const waiter of waiters)
|
|
1604
|
+
waiter.resolve(result);
|
|
1605
|
+
}
|
|
1606
|
+
cancelModelSelectionWaiters() {
|
|
1607
|
+
for (const waiters of this.modelSelectionWaiters.values()) {
|
|
1608
|
+
for (const waiter of waiters)
|
|
1609
|
+
waiter.resolve(undefined);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1475
1612
|
drainApprovalDispatches() {
|
|
1476
1613
|
const cursor = this.deliveryStore.getApprovalDispatchCursor();
|
|
1477
1614
|
const dispatches = this.policy.listPendingApprovalDispatches(100, cursor.after);
|
|
@@ -1553,13 +1690,17 @@ export class AssistantDeliveryService extends Service {
|
|
|
1553
1690
|
return;
|
|
1554
1691
|
}
|
|
1555
1692
|
await Promise.allSettled(claims.map(async (claim) => {
|
|
1556
|
-
const finishWithoutReply = (result) =>
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1693
|
+
const finishWithoutReply = (result) => {
|
|
1694
|
+
const completed = this.deliveryStore.completeModelSelectionSettlement({
|
|
1695
|
+
operationId: claim.operationId,
|
|
1696
|
+
payloadHash: claim.payloadHash,
|
|
1697
|
+
result,
|
|
1698
|
+
ownerId: this.ownerId,
|
|
1699
|
+
fencingToken: claim.fencingToken,
|
|
1700
|
+
});
|
|
1701
|
+
this.resolveModelSelectionWaiters(claim.operationId, completed);
|
|
1702
|
+
return completed;
|
|
1703
|
+
};
|
|
1563
1704
|
const payload = claim.payload;
|
|
1564
1705
|
if (!isModelSelectionPayload(payload) || payload.bindingId !== claim.bindingId) {
|
|
1565
1706
|
finishWithoutReply({ status: 'rejected', reason: 'authorization-revoked' });
|
|
@@ -1589,7 +1730,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
1589
1730
|
format: 'plain',
|
|
1590
1731
|
});
|
|
1591
1732
|
const superseded = { status: 'rejected', reason: 'selection-superseded' };
|
|
1592
|
-
|
|
1733
|
+
const completed = this.deliveryStore.completeModelSelectionSettlement({
|
|
1593
1734
|
operationId: claim.operationId,
|
|
1594
1735
|
payloadHash: claim.payloadHash,
|
|
1595
1736
|
result,
|
|
@@ -1602,6 +1743,8 @@ export class AssistantDeliveryService extends Service {
|
|
|
1602
1743
|
ownerId: this.ownerId,
|
|
1603
1744
|
fencingToken: claim.fencingToken,
|
|
1604
1745
|
});
|
|
1746
|
+
this.resolveModelSelectionWaiters(claim.operationId, completed);
|
|
1747
|
+
return completed;
|
|
1605
1748
|
};
|
|
1606
1749
|
const initialBinding = this.deliveryStore.getBinding(claim.bindingId);
|
|
1607
1750
|
if (!ownsBinding(initialBinding) || !authorized(initialBinding)) {
|