@animalabs/agent-framework 0.26.2 → 0.28.0
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 +99 -8
- package/dist/src/agent.d.ts +291 -23
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +596 -61
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +5 -4
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/auto-continue.d.ts +34 -0
- package/dist/src/auto-continue.d.ts.map +1 -0
- package/dist/src/auto-continue.js +36 -0
- package/dist/src/auto-continue.js.map +1 -0
- package/dist/src/code-execution/isolate/scripts.d.ts +11 -0
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +5 -1
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/cut-off-round.d.ts +24 -0
- package/dist/src/cut-off-round.d.ts.map +1 -0
- package/dist/src/cut-off-round.js +87 -0
- package/dist/src/cut-off-round.js.map +1 -0
- package/dist/src/framework.d.ts +1038 -193
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +5967 -1514
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +405 -46
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +2646 -399
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +3 -2
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js +2 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/recipe-reconcile.d.ts +103 -0
- package/dist/src/gate/recipe-reconcile.d.ts.map +1 -0
- package/dist/src/gate/recipe-reconcile.js +325 -0
- package/dist/src/gate/recipe-reconcile.js.map +1 -0
- package/dist/src/gate/reply-judge.d.ts +116 -0
- package/dist/src/gate/reply-judge.d.ts.map +1 -0
- package/dist/src/gate/reply-judge.js +467 -0
- package/dist/src/gate/reply-judge.js.map +1 -0
- package/dist/src/gate/types.d.ts +229 -8
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.d.ts +184 -0
- package/dist/src/heartbeat/heartbeat.d.ts.map +1 -0
- package/dist/src/heartbeat/heartbeat.js +752 -0
- package/dist/src/heartbeat/heartbeat.js.map +1 -0
- package/dist/src/heartbeat/reminder-time.d.ts +5 -0
- package/dist/src/heartbeat/reminder-time.d.ts.map +1 -0
- package/dist/src/heartbeat/reminder-time.js +107 -0
- package/dist/src/heartbeat/reminder-time.js.map +1 -0
- package/dist/src/index.d.ts +10 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/capability-mask.d.ts.map +1 -1
- package/dist/src/mcpl/capability-mask.js +3 -2
- package/dist/src/mcpl/capability-mask.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +433 -113
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +1154 -435
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts +16 -8
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +29 -15
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts.map +1 -1
- package/dist/src/mcpl/convert-block.js +14 -2
- package/dist/src/mcpl/convert-block.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts +7 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.js +32 -7
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +26 -10
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +40 -69
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/index.d.ts +5 -1
- package/dist/src/mcpl/index.d.ts.map +1 -1
- package/dist/src/mcpl/index.js +4 -0
- package/dist/src/mcpl/index.js.map +1 -1
- package/dist/src/mcpl/inference-router.d.ts +14 -2
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/inference-router.js +73 -28
- package/dist/src/mcpl/inference-router.js.map +1 -1
- package/dist/src/mcpl/prose-grammar.d.ts +4 -0
- package/dist/src/mcpl/prose-grammar.d.ts.map +1 -1
- package/dist/src/mcpl/prose-grammar.js +6 -0
- package/dist/src/mcpl/prose-grammar.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +3 -0
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +20 -2
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +33 -11
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/reference-fetcher.d.ts +63 -42
- package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
- package/dist/src/mcpl/reference-fetcher.js +177 -145
- package/dist/src/mcpl/reference-fetcher.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +380 -46
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +736 -77
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +93 -4
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +176 -11
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tags.d.ts +9 -2
- package/dist/src/mcpl/tags.d.ts.map +1 -1
- package/dist/src/mcpl/tags.js +12 -2
- package/dist/src/mcpl/tags.js.map +1 -1
- package/dist/src/mcpl/tool-lifecycle.d.ts +11 -0
- package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
- package/dist/src/mcpl/tool-lifecycle.js +21 -0
- package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
- package/dist/src/mcpl/tool-presence.d.ts +311 -0
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -0
- package/dist/src/mcpl/tool-presence.js +697 -0
- package/dist/src/mcpl/tool-presence.js.map +1 -0
- package/dist/src/mcpl/tool-result-normalize.d.ts +9 -3
- package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -1
- package/dist/src/mcpl/tool-result-normalize.js +14 -6
- package/dist/src/mcpl/tool-result-normalize.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts +46 -0
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -0
- package/dist/src/mcpl/tool-signature.js +321 -0
- package/dist/src/mcpl/tool-signature.js.map +1 -0
- package/dist/src/mcpl/toolset-host.d.ts +251 -0
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -0
- package/dist/src/mcpl/toolset-host.js +720 -0
- package/dist/src/mcpl/toolset-host.js.map +1 -0
- package/dist/src/mcpl/toolsets-module.d.ts +83 -0
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -0
- package/dist/src/mcpl/toolsets-module.js +523 -0
- package/dist/src/mcpl/toolsets-module.js.map +1 -0
- package/dist/src/mcpl/transport.d.ts +2 -2
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +3 -2
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +24 -2
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/mcpl/visible-content.d.ts +7 -9
- package/dist/src/mcpl/visible-content.d.ts.map +1 -1
- package/dist/src/mcpl/visible-content.js +21 -9
- package/dist/src/mcpl/visible-content.js.map +1 -1
- package/dist/src/module-registry.d.ts +27 -9
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +77 -17
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/discord/index.js +1 -1
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts +2 -1
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +73 -28
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/history/semantic.d.ts +108 -6
- package/dist/src/modules/history/semantic.d.ts.map +1 -1
- package/dist/src/modules/history/semantic.js +273 -57
- package/dist/src/modules/history/semantic.js.map +1 -1
- package/dist/src/modules/workspace/answers.d.ts +22 -0
- package/dist/src/modules/workspace/answers.d.ts.map +1 -0
- package/dist/src/modules/workspace/answers.js +123 -0
- package/dist/src/modules/workspace/answers.js.map +1 -0
- package/dist/src/modules/workspace/branch-intent.d.ts +5 -3
- package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
- package/dist/src/modules/workspace/branch-intent.js +6 -2
- package/dist/src/modules/workspace/branch-intent.js.map +1 -1
- package/dist/src/modules/workspace/direct-write.d.ts +184 -0
- package/dist/src/modules/workspace/direct-write.d.ts.map +1 -0
- package/dist/src/modules/workspace/direct-write.js +417 -0
- package/dist/src/modules/workspace/direct-write.js.map +1 -0
- package/dist/src/modules/workspace/disk-agreement.d.ts +33 -11
- package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +48 -17
- package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
- package/dist/src/modules/workspace/edit-text.d.ts +27 -12
- package/dist/src/modules/workspace/edit-text.d.ts.map +1 -1
- package/dist/src/modules/workspace/edit-text.js +61 -29
- package/dist/src/modules/workspace/edit-text.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts +10 -0
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +21 -14
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +308 -78
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +2103 -720
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +75 -63
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +366 -180
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/read-page.d.ts +41 -0
- package/dist/src/modules/workspace/read-page.d.ts.map +1 -0
- package/dist/src/modules/workspace/read-page.js +219 -0
- package/dist/src/modules/workspace/read-page.js.map +1 -0
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +26 -16
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/modules/workspace/result-text.d.ts +68 -0
- package/dist/src/modules/workspace/result-text.d.ts.map +1 -0
- package/dist/src/modules/workspace/result-text.js +94 -0
- package/dist/src/modules/workspace/result-text.js.map +1 -0
- package/dist/src/modules/workspace/sync.d.ts +9 -0
- package/dist/src/modules/workspace/sync.d.ts.map +1 -1
- package/dist/src/modules/workspace/sync.js +14 -0
- package/dist/src/modules/workspace/sync.js.map +1 -1
- package/dist/src/modules/workspace/text.d.ts +29 -12
- package/dist/src/modules/workspace/text.d.ts.map +1 -1
- package/dist/src/modules/workspace/text.js +35 -33
- package/dist/src/modules/workspace/text.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +137 -60
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
- package/dist/src/modules/workspace/watcher.js +8 -3
- package/dist/src/modules/workspace/watcher.js.map +1 -1
- package/dist/src/prose-drafts.d.ts +4 -3
- package/dist/src/prose-drafts.d.ts.map +1 -1
- package/dist/src/prose-drafts.js +3 -2
- package/dist/src/prose-drafts.js.map +1 -1
- package/dist/src/reasoning-loss-notice.d.ts +23 -0
- package/dist/src/reasoning-loss-notice.d.ts.map +1 -0
- package/dist/src/reasoning-loss-notice.js +50 -0
- package/dist/src/reasoning-loss-notice.js.map +1 -0
- package/dist/src/reasoning-loss.d.ts +24 -0
- package/dist/src/reasoning-loss.d.ts.map +1 -0
- package/dist/src/reasoning-loss.js +102 -0
- package/dist/src/reasoning-loss.js.map +1 -0
- package/dist/src/send-tools.d.ts +3 -2
- package/dist/src/send-tools.d.ts.map +1 -1
- package/dist/src/send-tools.js +3 -2
- package/dist/src/send-tools.js.map +1 -1
- package/dist/src/sent-rows.d.ts +59 -0
- package/dist/src/sent-rows.d.ts.map +1 -0
- package/dist/src/sent-rows.js +145 -0
- package/dist/src/sent-rows.js.map +1 -0
- package/dist/src/silent-heartbeat.d.ts +17 -10
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/stable-hash.d.ts +3 -0
- package/dist/src/stable-hash.d.ts.map +1 -0
- package/dist/src/stable-hash.js +15 -0
- package/dist/src/stable-hash.js.map +1 -0
- package/dist/src/stored-injections.d.ts +99 -0
- package/dist/src/stored-injections.d.ts.map +1 -0
- package/dist/src/stored-injections.js +106 -0
- package/dist/src/stored-injections.js.map +1 -0
- package/dist/src/text-as-stored.d.ts +4 -3
- package/dist/src/text-as-stored.d.ts.map +1 -1
- package/dist/src/text-as-stored.js +4 -3
- package/dist/src/text-as-stored.js.map +1 -1
- package/dist/src/thinking-drops.d.ts +74 -0
- package/dist/src/thinking-drops.d.ts.map +1 -0
- package/dist/src/thinking-drops.js +96 -0
- package/dist/src/thinking-drops.js.map +1 -0
- package/dist/src/tool-args.d.ts +12 -0
- package/dist/src/tool-args.d.ts.map +1 -1
- package/dist/src/tool-args.js +35 -1
- package/dist/src/tool-args.js.map +1 -1
- package/dist/src/tool-image-ledger.d.ts +9 -8
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +9 -8
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +3 -1
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +12 -6
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +44 -17
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tool-result-history.d.ts +176 -24
- package/dist/src/tool-result-history.d.ts.map +1 -1
- package/dist/src/tool-result-history.js +411 -91
- package/dist/src/tool-result-history.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +17 -9
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +84 -37
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +4 -1
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/types/agent.d.ts +217 -7
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +33 -9
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +39 -3
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +95 -9
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +91 -3
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/agent.ts +661 -67
- package/src/api/server.ts +5 -4
- package/src/auto-continue.ts +43 -0
- package/src/code-execution/isolate/scripts.ts +11 -3
- package/src/cut-off-round.ts +98 -0
- package/src/framework.ts +6168 -1540
- package/src/gate/event-gate.ts +2895 -405
- package/src/gate/index.ts +10 -1
- package/src/gate/recipe-reconcile.ts +355 -0
- package/src/gate/reply-judge.ts +512 -0
- package/src/gate/types.ts +234 -9
- package/src/heartbeat/heartbeat.ts +804 -0
- package/src/heartbeat/reminder-time.ts +107 -0
- package/src/index.ts +11 -4
- package/src/mcpl/capability-mask.ts +3 -2
- package/src/mcpl/channel-registry.ts +1222 -468
- package/src/mcpl/conversation-router.ts +34 -16
- package/src/mcpl/convert-block.ts +14 -1
- package/src/mcpl/hook-orchestrator.ts +33 -6
- package/src/mcpl/inbound-source.ts +42 -69
- package/src/mcpl/index.ts +6 -1
- package/src/mcpl/inference-router.ts +79 -34
- package/src/mcpl/prose-grammar.ts +7 -0
- package/src/mcpl/protocol-family.ts +2 -0
- package/src/mcpl/push-coalescer.ts +51 -13
- package/src/mcpl/reference-fetcher.ts +201 -162
- package/src/mcpl/references.ts +851 -77
- package/src/mcpl/server-connection.ts +189 -11
- package/src/mcpl/tags.ts +13 -2
- package/src/mcpl/tool-lifecycle.ts +21 -0
- package/src/mcpl/tool-presence.ts +716 -0
- package/src/mcpl/tool-result-normalize.ts +18 -6
- package/src/mcpl/tool-signature.ts +316 -0
- package/src/mcpl/toolset-host.ts +727 -0
- package/src/mcpl/toolsets-module.ts +545 -0
- package/src/mcpl/transport.ts +3 -2
- package/src/mcpl/types.ts +25 -2
- package/src/mcpl/visible-content.ts +22 -9
- package/src/module-registry.ts +85 -26
- package/src/modules/discord/index.ts +1 -1
- package/src/modules/history/index.ts +70 -30
- package/src/modules/history/semantic.ts +308 -54
- package/src/modules/workspace/answers.ts +133 -0
- package/src/modules/workspace/branch-intent.ts +10 -5
- package/src/modules/workspace/direct-write.ts +460 -0
- package/src/modules/workspace/disk-agreement.ts +52 -21
- package/src/modules/workspace/edit-text.ts +84 -35
- package/src/modules/workspace/effects.ts +22 -13
- package/src/modules/workspace/index.ts +2142 -723
- package/src/modules/workspace/observe.ts +381 -177
- package/src/modules/workspace/read-page.ts +228 -0
- package/src/modules/workspace/reconcile.ts +27 -15
- package/src/modules/workspace/result-text.ts +121 -0
- package/src/modules/workspace/sync.ts +16 -0
- package/src/modules/workspace/text.ts +58 -32
- package/src/modules/workspace/types.ts +137 -60
- package/src/modules/workspace/watcher.ts +8 -3
- package/src/prose-drafts.ts +4 -3
- package/src/reasoning-loss-notice.ts +62 -0
- package/src/reasoning-loss.ts +120 -0
- package/src/send-tools.ts +3 -2
- package/src/sent-rows.ts +162 -0
- package/src/silent-heartbeat.ts +17 -10
- package/src/stable-hash.ts +14 -0
- package/src/stored-injections.ts +131 -0
- package/src/text-as-stored.ts +4 -3
- package/src/thinking-drops.ts +135 -0
- package/src/tool-args.ts +34 -1
- package/src/tool-image-ledger.ts +9 -8
- package/src/tool-presentation.ts +3 -1
- package/src/tool-result-guard.ts +45 -17
- package/src/tool-result-history.ts +471 -90
- package/src/tune-out/coordinator.ts +80 -37
- package/src/tune-out/tools.ts +4 -1
- package/src/types/agent.ts +220 -7
- package/src/types/events.ts +33 -9
- package/src/types/framework.ts +36 -3
- package/src/types/module.ts +91 -9
- package/src/types/trace.ts +94 -3
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
import { createHash } from 'node:crypto';
|
|
38
38
|
import type { ContextStrategy } from '@animalabs/context-manager';
|
|
39
39
|
import type { ChannelDescriptor } from './types.js';
|
|
40
|
+
import { destinationKey, type ChannelRef } from './channel-registry.js';
|
|
40
41
|
|
|
41
42
|
// ============================================================================
|
|
42
43
|
// Config & types
|
|
@@ -108,6 +109,9 @@ export interface ConversationRouterConfig {
|
|
|
108
109
|
|
|
109
110
|
export interface ConversationBinding {
|
|
110
111
|
channelId: string;
|
|
112
|
+
/** The channel's server: channel ids are unique only within one server's
|
|
113
|
+
* connection (MCPL §14.2), so each server's channel has its own binding. */
|
|
114
|
+
serverId: string;
|
|
111
115
|
agentName: string;
|
|
112
116
|
/** Engagement counter per channel — fresh forks get a new generation. */
|
|
113
117
|
generation: number;
|
|
@@ -157,11 +161,15 @@ export const DEFAULT_CLOSURE_PROMPT =
|
|
|
157
161
|
export class ConversationRouter {
|
|
158
162
|
private config: ConversationRouterConfig;
|
|
159
163
|
|
|
160
|
-
/** Active bindings, keyed by
|
|
164
|
+
/** Active bindings, keyed by the channel's server and id (destinationKey):
|
|
165
|
+
* two servers' channels with one id are two conversations. */
|
|
161
166
|
private bindings = new Map<string, ConversationBinding>();
|
|
162
167
|
|
|
163
|
-
/** Engagement counter per channel — survives unbind, so a re-bound
|
|
164
|
-
* gets a distinct fork agent name.
|
|
168
|
+
/** Engagement counter per channel id — survives unbind, so a re-bound
|
|
169
|
+
* channel gets a distinct fork agent name. Kept by bare id, as it's
|
|
170
|
+
* persisted: two servers sharing an id share a counter, which still never
|
|
171
|
+
* repeats a name, and their forks' names differ anyway, since each hashes
|
|
172
|
+
* its server (forkAgentName). */
|
|
165
173
|
private generations = new Map<string, number>();
|
|
166
174
|
|
|
167
175
|
constructor(config: ConversationRouterConfig) {
|
|
@@ -191,7 +199,7 @@ export class ConversationRouter {
|
|
|
191
199
|
* unchanged; a failed spawn doesn't leave a dangling route.
|
|
192
200
|
*/
|
|
193
201
|
route(facts: IncomingMessageFacts): RoutingDecision {
|
|
194
|
-
const existing = this.bindings.get(facts.channelId);
|
|
202
|
+
const existing = this.bindings.get(bindingKey({ serverId: facts.serverId ?? '', channelId: facts.channelId }));
|
|
195
203
|
|
|
196
204
|
if (existing) {
|
|
197
205
|
return {
|
|
@@ -223,39 +231,44 @@ export class ConversationRouter {
|
|
|
223
231
|
* Record a binding after the framework successfully spawned the fork.
|
|
224
232
|
* Returns the new binding.
|
|
225
233
|
*/
|
|
226
|
-
bind(
|
|
227
|
-
|
|
234
|
+
bind(channel: ChannelRef, agentName: string, generation: number, now = Date.now()): ConversationBinding {
|
|
235
|
+
// The counter is kept by bare id, and two servers' spawns on one id run
|
|
236
|
+
// beside each other, so a bind can finish after a later spawn's: the
|
|
237
|
+
// counter keeps the higher, or the next spawn would propose a fork name
|
|
238
|
+
// already used.
|
|
239
|
+
this.generations.set(channel.channelId, Math.max(this.generations.get(channel.channelId) ?? 0, generation));
|
|
228
240
|
const binding: ConversationBinding = {
|
|
229
|
-
channelId,
|
|
241
|
+
channelId: channel.channelId,
|
|
242
|
+
serverId: channel.serverId,
|
|
230
243
|
agentName,
|
|
231
244
|
generation,
|
|
232
245
|
boundAt: now,
|
|
233
246
|
lastActivity: now,
|
|
234
247
|
};
|
|
235
|
-
this.bindings.set(
|
|
248
|
+
this.bindings.set(bindingKey(channel), binding);
|
|
236
249
|
return binding;
|
|
237
250
|
}
|
|
238
251
|
|
|
239
252
|
/** Record delivered activity on an existing binding. Does not bind a
|
|
240
253
|
* channel or advance its generation. Call after successful delivery, not
|
|
241
254
|
* when previewing a route. The optional clock supports deterministic use. */
|
|
242
|
-
touch(
|
|
243
|
-
const binding = this.bindings.get(
|
|
255
|
+
touch(channel: ChannelRef, now = Date.now()): void {
|
|
256
|
+
const binding = this.bindings.get(bindingKey(channel));
|
|
244
257
|
if (binding) binding.lastActivity = now;
|
|
245
258
|
}
|
|
246
259
|
|
|
247
|
-
unbind(
|
|
248
|
-
this.bindings.delete(
|
|
260
|
+
unbind(channel: ChannelRef): void {
|
|
261
|
+
this.bindings.delete(bindingKey(channel));
|
|
249
262
|
}
|
|
250
263
|
|
|
251
|
-
getBinding(
|
|
252
|
-
return this.bindings.get(
|
|
264
|
+
getBinding(channel: ChannelRef): ConversationBinding | undefined {
|
|
265
|
+
return this.bindings.get(bindingKey(channel));
|
|
253
266
|
}
|
|
254
267
|
|
|
255
268
|
/** Reverse lookup: the channel an agent is bound to, if any. */
|
|
256
|
-
channelForAgent(agentName: string):
|
|
269
|
+
channelForAgent(agentName: string): ChannelRef | undefined {
|
|
257
270
|
for (const binding of this.bindings.values()) {
|
|
258
|
-
if (binding.agentName === agentName) return binding.channelId;
|
|
271
|
+
if (binding.agentName === agentName) return { serverId: binding.serverId, channelId: binding.channelId };
|
|
259
272
|
}
|
|
260
273
|
return undefined;
|
|
261
274
|
}
|
|
@@ -325,6 +338,11 @@ export class ConversationRouter {
|
|
|
325
338
|
/** The readable part of a fork's name stops here: uniqueness rides on the hash. */
|
|
326
339
|
const FORK_NAME_READABLE_MAX = 64;
|
|
327
340
|
|
|
341
|
+
/** A binding's key: its channel's server and id (destinationKey). */
|
|
342
|
+
function bindingKey(channel: ChannelRef): string {
|
|
343
|
+
return destinationKey(channel);
|
|
344
|
+
}
|
|
345
|
+
|
|
328
346
|
/**
|
|
329
347
|
* A fork's agent name: `<prefix>-<readable>-g<generation>-<hash>`, such as
|
|
330
348
|
* `conversation-C123-g2-e08e564271867d16` for Slack's channel `C123` on the
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
IMAGE_NO_DATA_TEXT,
|
|
12
12
|
IMAGE_NO_MEDIA_TYPE_TEXT,
|
|
13
13
|
INLINE_WITHHELD_TEXT,
|
|
14
|
+
classifyBlock,
|
|
14
15
|
isInlineContradiction,
|
|
15
16
|
referenceStubOrNull,
|
|
16
17
|
unknownBlockText,
|
|
@@ -85,12 +86,24 @@ export function convertBlock(block: McplContentBlock, provenance: string, server
|
|
|
85
86
|
}
|
|
86
87
|
return { type: 'text', text: isFilledString(block.data) ? AUDIO_NO_MEDIA_TYPE_TEXT : AUDIO_NO_DATA_TEXT };
|
|
87
88
|
|
|
88
|
-
case 'resource':
|
|
89
|
+
case 'resource': {
|
|
90
|
+
// MCP's embedded resource shows as the block its contents make would
|
|
91
|
+
// show here (RFC-005 §8.1), its uri never.
|
|
92
|
+
const c = classifyBlock(block);
|
|
93
|
+
if (c.kind === 'embedded' && c.contents.kind === 'block') {
|
|
94
|
+
return convertBlock(c.contents.block as McplContentBlock, provenance, serverId);
|
|
95
|
+
}
|
|
89
96
|
// RFC-005: reference blocks become bounded stubs — never raw URIs
|
|
90
97
|
// (a signed URL is a bearer credential that looks like a location).
|
|
91
98
|
return { type: 'text', text: referenceStubOrNull(block, provenance, serverId) ?? '[reference]' };
|
|
99
|
+
}
|
|
92
100
|
|
|
93
101
|
default: {
|
|
102
|
+
// MCP's link to a resource isn't one of MCPL's own blocks, but a server
|
|
103
|
+
// can send one, and RFC-005 §8.1 makes it a reference, shown as one.
|
|
104
|
+
if ((block as { type?: unknown }).type === 'resource_link') {
|
|
105
|
+
return { type: 'text', text: referenceStubOrNull(block, provenance, serverId) ?? '[reference]' };
|
|
106
|
+
}
|
|
94
107
|
// Unknown wire block types previously fell off the exhaustive switch
|
|
95
108
|
// and propagated `undefined` into ContentBlock[]. Fail visibly. A type
|
|
96
109
|
// that isn't a string is no type, and is never converted to one: an
|
|
@@ -71,7 +71,9 @@ function convertContent(content: string | McplContentBlock[], serverId: string):
|
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
* Convert an MCPL wire-format context injection, from the server `serverId`,
|
|
74
|
-
* to a context-manager ContextInjection.
|
|
74
|
+
* to a context-manager ContextInjection. It builds only these four fields,
|
|
75
|
+
* so a server can't opt an injection out of being stored
|
|
76
|
+
* (ModuleContextInjection.placedEachCompile, which only a module may set).
|
|
75
77
|
*/
|
|
76
78
|
function convertMcplInjection(mcplInj: McplContextInjection, serverId: string): ContextInjection {
|
|
77
79
|
return {
|
|
@@ -114,8 +116,17 @@ export class HookOrchestrator {
|
|
|
114
116
|
*
|
|
115
117
|
* Returns aggregated ContextInjection[] ready for context-manager's compile().
|
|
116
118
|
* Fail-open: servers that time out or error are silently skipped.
|
|
119
|
+
*
|
|
120
|
+
* `channelsFor` gives each server its own channel context (§14.4), in
|
|
121
|
+
* place of `params.channels`: channel ids are unique only within one
|
|
122
|
+
* server's connection (§14.2), so each server is told only of its own
|
|
123
|
+
* channels (ChannelRegistry.buildChannelContext). Undefined from it leaves
|
|
124
|
+
* the field out for that server.
|
|
117
125
|
*/
|
|
118
|
-
async beforeInference(
|
|
126
|
+
async beforeInference(
|
|
127
|
+
params: BeforeInferenceParams,
|
|
128
|
+
channelsFor?: (serverId: string) => BeforeInferenceParams['channels'],
|
|
129
|
+
): Promise<ContextInjection[]> {
|
|
119
130
|
// §5.4: the GRANT decides fan-out, not the raw advertisement — which
|
|
120
131
|
// also fixes selection for the recursive §5.1 shape, where
|
|
121
132
|
// `beforeInference: {observe: true, …}` is an object, not `true`. A
|
|
@@ -132,7 +143,7 @@ export class HookOrchestrator {
|
|
|
132
143
|
|
|
133
144
|
this._isInHook = true;
|
|
134
145
|
try {
|
|
135
|
-
return await this.fanOutBeforeInference(servers, params);
|
|
146
|
+
return await this.fanOutBeforeInference(servers, params, channelsFor);
|
|
136
147
|
} finally {
|
|
137
148
|
this._isInHook = false;
|
|
138
149
|
}
|
|
@@ -163,17 +174,33 @@ export class HookOrchestrator {
|
|
|
163
174
|
private async fanOutBeforeInference(
|
|
164
175
|
servers: McplServerConnection[],
|
|
165
176
|
params: BeforeInferenceParams,
|
|
177
|
+
channelsFor?: (serverId: string) => BeforeInferenceParams['channels'],
|
|
166
178
|
): Promise<ContextInjection[]> {
|
|
167
179
|
const results = await Promise.allSettled(
|
|
168
|
-
|
|
180
|
+
// Async, so a throw building one server's channel context fails that
|
|
181
|
+
// server alone, as its timeout or transport error would.
|
|
182
|
+
servers.map(async (server) => {
|
|
183
|
+
let base = params;
|
|
184
|
+
if (channelsFor) {
|
|
185
|
+
const { channels: _shared, ...rest } = params;
|
|
186
|
+
let channels: BeforeInferenceParams['channels'];
|
|
187
|
+
try {
|
|
188
|
+
channels = channelsFor(server.id);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
// A bug, not a server's failure: said, then this server is skipped.
|
|
191
|
+
console.error(`[beforeInference] the channel context for "${server.id}" couldn't be built, so it isn't called:`, err);
|
|
192
|
+
throw err;
|
|
193
|
+
}
|
|
194
|
+
base = channels ? { ...rest, channels } : rest;
|
|
195
|
+
}
|
|
169
196
|
// §10.1: a server not granted observe MUST receive
|
|
170
197
|
// `userMessage: null` — the field is ABSENT authority, not merely
|
|
171
198
|
// discouraged — while the hook is still invoked so granted
|
|
172
199
|
// injection positions keep working (write-without-read).
|
|
173
200
|
const perServer: BeforeInferenceParams =
|
|
174
201
|
CapabilityGrant.of(server).has('contextHooks.beforeInference.observe')
|
|
175
|
-
?
|
|
176
|
-
: { ...
|
|
202
|
+
? base
|
|
203
|
+
: { ...base, userMessage: null };
|
|
177
204
|
return withTimeout(
|
|
178
205
|
server.sendBeforeInference(perServer),
|
|
179
206
|
BEFORE_INFERENCE_TIMEOUT_MS,
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { createHash } from 'node:crypto';
|
|
25
|
+
import { blockAsStored, MalformedContentError } from '@animalabs/context-manager';
|
|
26
|
+
import { logDetail, logValue } from './log-value.js';
|
|
25
27
|
|
|
26
28
|
/** The MCPL admission lane that accepted an item. Its contract decides what
|
|
27
29
|
* an `eventId` is worth: `push/event` deduplicates by eventId, and RFC-006
|
|
@@ -192,68 +194,23 @@ export function canonicalJson(value: unknown): string {
|
|
|
192
194
|
}
|
|
193
195
|
|
|
194
196
|
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* 'base64', data, mediaType } }` and nothing else, its data re-encoded from
|
|
199
|
-
* the decoded bytes, and an image's media type taken from its bytes'
|
|
200
|
-
* signature where they have one. Any other block comes back as it was given,
|
|
201
|
-
* except a store-internal `blob_ref` from an unresolved read, which resolves
|
|
202
|
-
* to inline media by default. It is digested as given here, so an unresolved
|
|
203
|
-
* read and its resolved form differ. Context-manager refuses caller-supplied
|
|
204
|
-
* blob_ref writes; the MCPL lanes never store one.
|
|
197
|
+
* blockAsStored throws a `MalformedContentError` for a block the store
|
|
198
|
+
* refuses on write, a caller-supplied `blob_ref` among them. Such a block
|
|
199
|
+
* has no stored form, and whatever is thrown, it is digested as given.
|
|
205
200
|
*/
|
|
206
201
|
function storedBlock(block: unknown): unknown {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Base64 as the store writes it back from the decoded bytes: padded, with no
|
|
222
|
-
* whitespace, the standard alphabet and zero unused bits. Data already in
|
|
223
|
-
* that form, as every stored copy's is, is returned without decoding it all.
|
|
224
|
-
*/
|
|
225
|
-
function canonicalBase64(data: string): string {
|
|
226
|
-
if (data.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(data)) {
|
|
227
|
-
const last = data.slice(-4);
|
|
228
|
-
if (Buffer.from(last, 'base64').toString('base64') === last) return data;
|
|
229
|
-
}
|
|
230
|
-
return Buffer.from(data, 'base64').toString('base64');
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* The store's own test for raster image types with an unambiguous byte
|
|
235
|
-
* signature, copied from context-manager's blob-manager.ts (it isn't
|
|
236
|
-
* exported): the store relabels an image whose bytes match one of these.
|
|
237
|
-
*/
|
|
238
|
-
function sniffRasterImageMediaType(bytes: Uint8Array): string | null {
|
|
239
|
-
if (bytes.length >= 8 &&
|
|
240
|
-
bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47 &&
|
|
241
|
-
bytes[4] === 0x0d && bytes[5] === 0x0a && bytes[6] === 0x1a && bytes[7] === 0x0a) {
|
|
242
|
-
return 'image/png';
|
|
243
|
-
}
|
|
244
|
-
if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
|
|
245
|
-
return 'image/jpeg';
|
|
246
|
-
}
|
|
247
|
-
if (bytes.length >= 6) {
|
|
248
|
-
const signature = Buffer.from(bytes.subarray(0, 6)).toString('ascii');
|
|
249
|
-
if (signature === 'GIF87a' || signature === 'GIF89a') return 'image/gif';
|
|
250
|
-
}
|
|
251
|
-
if (bytes.length >= 12 &&
|
|
252
|
-
Buffer.from(bytes.subarray(0, 4)).toString('ascii') === 'RIFF' &&
|
|
253
|
-
Buffer.from(bytes.subarray(8, 12)).toString('ascii') === 'WEBP') {
|
|
254
|
-
return 'image/webp';
|
|
202
|
+
try {
|
|
203
|
+
return blockAsStored(block as Parameters<typeof blockAsStored>[0]);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
if (!(err instanceof MalformedContentError)) {
|
|
206
|
+
const { name, message } = (err ?? {}) as { name?: unknown; message?: unknown };
|
|
207
|
+
console.error(
|
|
208
|
+
`[stored-digest-as-given] error=${logValue(typeof name === 'string' ? name : typeof err)}: ` +
|
|
209
|
+
logDetail(typeof message === 'string' ? message : ''),
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return block;
|
|
255
213
|
}
|
|
256
|
-
return null;
|
|
257
214
|
}
|
|
258
215
|
|
|
259
216
|
/**
|
|
@@ -261,23 +218,39 @@ function sniffRasterImageMediaType(bytes: Uint8Array): string | null {
|
|
|
261
218
|
* room-220 #46752–#47210): SHA-256 hex of `canonicalJson([blocks])` over the
|
|
262
219
|
* ContentBlock[] the framework stores for the body — after MCPL conversion,
|
|
263
220
|
* before any host decoration is added and before storage shards it — with
|
|
264
|
-
* each block as the store keeps it
|
|
265
|
-
* hashes alike as delivered, as handed to storage, and as
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
221
|
+
* each block as the store keeps it, which context-manager's `blockAsStored`
|
|
222
|
+
* gives. So a body hashes alike as delivered, as handed to storage, and as
|
|
223
|
+
* read back unsharded from the store with its blobs resolved, except one
|
|
224
|
+
* whose blocks the store changes together: when it mends a `tool_use` whose
|
|
225
|
+
* input isn't an object, it also releases a raw replay form (`rawXml`,
|
|
226
|
+
* `rawItem`) that the message's other blocks share, which no one block's
|
|
227
|
+
* stored form shows. A body an MCPL lane stamps is never one. A block the
|
|
228
|
+
* store refuses on write, a `blob_ref` among them, is digested as given. The
|
|
229
|
+
* one-element array keeps the framing an undecorated, unsharded stored copy
|
|
230
|
+
* has always hashed to, so a stamped copy and an older copy of the same body
|
|
231
|
+
* share a version where the store keeps the body as it did then.
|
|
232
|
+
*
|
|
233
|
+
* A digest is a digest under the context-manager release that computed it: a
|
|
234
|
+
* release that changes what the store keeps, such as its set of image
|
|
235
|
+
* signatures, changes the digest of a body it touches.
|
|
269
236
|
*
|
|
270
237
|
* Ingestion stamps it as `metadata.sourceBodyDigest`, and the same function
|
|
271
238
|
* over exactly the blocks handed to storage (decorations included) as
|
|
272
239
|
* `metadata.storedBodyDigest`, on both MCPL lanes. Both live outside the
|
|
273
240
|
* frozen admission envelope: they describe the body actually delivered (a
|
|
274
241
|
* materialization, a correction), not the admission. Neither proves later
|
|
275
|
-
* presence or completeness
|
|
242
|
+
* presence or completeness. An unsharded stored copy that no longer hashes
|
|
276
243
|
* to its storedBodyDigest was changed after delivery (editMessage keeps
|
|
277
|
-
* metadata)
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
244
|
+
* metadata), or holds blocks the store changes together, or its digest was
|
|
245
|
+
* taken under a release that kept some block of it differently: a
|
|
246
|
+
* context-manager release that changed a stored form, or an agent-framework
|
|
247
|
+
* release from 0.24.0 up to the one that brought in blockAsStored, which
|
|
248
|
+
* took stored forms from a copy of the store's transform. Digests already
|
|
249
|
+
* stamped are never recomputed. A body the store shards carries the whole
|
|
250
|
+
* body's two digests on every shard, and no shard hashes to them. Check a
|
|
251
|
+
* copy read back with its blobs resolved (getAllMessages, getMessage, or
|
|
252
|
+
* getMessageWindow without `resolveBlobs: false`): a blob reference never
|
|
253
|
+
* matches.
|
|
281
254
|
*
|
|
282
255
|
* Exported from the package root, so a consumer checks a copy against either
|
|
283
256
|
* field with this same function rather than a copy of it.
|
package/src/mcpl/index.ts
CHANGED
|
@@ -88,6 +88,11 @@ export type {
|
|
|
88
88
|
} from './types.js';
|
|
89
89
|
|
|
90
90
|
export { McplMethod } from './types.js';
|
|
91
|
+
// RFC-014 tool presence and toolsets (host side).
|
|
92
|
+
export { ToolsetHub, ToolsetController, TOOLSETS_MODULE, META_TOOL_NAMES } from './toolset-host.js';
|
|
93
|
+
export { ToolsetsModule, TOOLSETS_REMINDER_NAMESPACE } from './toolsets-module.js';
|
|
94
|
+
export { ToolPresence } from './tool-presence.js';
|
|
95
|
+
export type { ChangeSet, MetaToolNames, ToolsetDecl } from './tool-presence.js';
|
|
91
96
|
|
|
92
97
|
// Error codes and factories
|
|
93
98
|
export {
|
|
@@ -171,7 +176,7 @@ export { InferenceRouter } from './inference-router.js';
|
|
|
171
176
|
|
|
172
177
|
// Channel registry (channel lifecycle, incoming messages, synthesized tools)
|
|
173
178
|
export { ChannelRegistry } from './channel-registry.js';
|
|
174
|
-
export type { PublishRefusalReason } from './channel-registry.js';
|
|
179
|
+
export type { ChannelRef, PublishRefusalReason } from './channel-registry.js';
|
|
175
180
|
|
|
176
181
|
// Inbound source envelope: where each accepted item came from, stamped once;
|
|
177
182
|
// and the digest of a delivered or stored body
|
|
@@ -26,6 +26,7 @@ import type {
|
|
|
26
26
|
import type { FeatureSetManager } from './feature-set-manager.js';
|
|
27
27
|
import { McplFeatureSetError } from './feature-set-manager.js';
|
|
28
28
|
import type { HookOrchestrator } from './hook-orchestrator.js';
|
|
29
|
+
import { logValue } from './log-value.js';
|
|
29
30
|
|
|
30
31
|
// ============================================================================
|
|
31
32
|
// Responder interface
|
|
@@ -181,6 +182,10 @@ export class InferenceRouter {
|
|
|
181
182
|
private featureSetManager: FeatureSetManager;
|
|
182
183
|
private routingPolicy: InferenceRoutingPolicy | null;
|
|
183
184
|
private emitTraceFn: (event: { type: string; [key: string]: unknown }) => void;
|
|
185
|
+
/** The refusals for want of a model that have been logged: one line per
|
|
186
|
+
* server with no policy, and per server and feature set with one, so a
|
|
187
|
+
* server that keeps asking doesn't flood the operator's log. */
|
|
188
|
+
private loggedRefusals = new Set<string>();
|
|
184
189
|
|
|
185
190
|
constructor(
|
|
186
191
|
membrane: Membrane,
|
|
@@ -201,7 +206,7 @@ export class InferenceRouter {
|
|
|
201
206
|
*
|
|
202
207
|
* 1. Validate feature set
|
|
203
208
|
* 2. Check loop prevention (reject if inside a hook)
|
|
204
|
-
* 3. Resolve model from routing policy
|
|
209
|
+
* 3. Resolve model from routing policy, refusing when it names none
|
|
205
210
|
* 4. Build membrane request
|
|
206
211
|
* 5. Run inference, streaming its visible text as chunks when asked
|
|
207
212
|
* 6. Answer with the result, or with the error for any ending that isn't
|
|
@@ -233,11 +238,62 @@ export class InferenceRouter {
|
|
|
233
238
|
return;
|
|
234
239
|
}
|
|
235
240
|
|
|
236
|
-
//
|
|
241
|
+
// Streaming needs a way to send chunks and an id to tag them with;
|
|
242
|
+
// without either, the request is answered whole.
|
|
243
|
+
const sendChunk = params.stream ? responder.sendChunk?.bind(responder) : undefined;
|
|
244
|
+
const chunkRequestId = sendChunk ? responder.requestId : undefined;
|
|
245
|
+
const streamed = chunkRequestId != null;
|
|
246
|
+
|
|
247
|
+
// 3. Resolve model. With no routing policy, or one that names no model
|
|
248
|
+
// for this feature set, the operator has chosen no model for this
|
|
249
|
+
// server's inference: refused rather than guessed, since a model picked
|
|
250
|
+
// here would run, on whatever credentials and at whatever price it
|
|
251
|
+
// carries, for a server the operator never chose it for. The answer is
|
|
252
|
+
// -32603, the code the router answers a failed inference with, as it
|
|
253
|
+
// answered this request when it reached the provider with no model; the
|
|
254
|
+
// server's grant can still list inferenceRequest, so not -32002.
|
|
237
255
|
const model = this.resolveModel(
|
|
238
256
|
params.featureSet,
|
|
239
257
|
params.conversationId,
|
|
240
258
|
);
|
|
259
|
+
if (model === undefined) {
|
|
260
|
+
const policy = Boolean(this.routingPolicy);
|
|
261
|
+
// One reason, quoted for where it goes: JSON for the server, logValue
|
|
262
|
+
// for the host log.
|
|
263
|
+
const reason = (quote: (value: string) => string) => policy
|
|
264
|
+
? `the inference routing policy (inferenceRouting) names no model for feature set ${quote(params.featureSet)}`
|
|
265
|
+
: 'the host sets no inference routing policy (inferenceRouting)';
|
|
266
|
+
const message = `inference/request refused: ${reason(JSON.stringify)}, so the host has no model to run it on; ` +
|
|
267
|
+
"it stays refused until the host's operator configures one";
|
|
268
|
+
// Logged once per server with no policy, and once per server and
|
|
269
|
+
// feature set with one, since each feature set the policy misses is
|
|
270
|
+
// its own entry to add; validateInbound (step 1) bounds feature sets
|
|
271
|
+
// to those the server declares.
|
|
272
|
+
const key = JSON.stringify(policy ? [serverId, params.featureSet] : [serverId]);
|
|
273
|
+
if (!this.loggedRefusals.has(key)) {
|
|
274
|
+
this.loggedRefusals.add(key);
|
|
275
|
+
console.error(
|
|
276
|
+
`[mcpl] ${logValue(serverId)}: inference/request refused: ${reason(logValue)}; ` +
|
|
277
|
+
(policy
|
|
278
|
+
? 'give it a default, or a byFeature or wildcards entry for that feature set. ' +
|
|
279
|
+
'This line prints once per server and feature set'
|
|
280
|
+
: 'set inferenceRouting in the framework config to choose the model server-initiated inference runs on. ' +
|
|
281
|
+
'This line prints once per server') +
|
|
282
|
+
'; later refusals are answered and traced but not logged.',
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
// The failure a trace watcher saw when this request failed at the
|
|
286
|
+
// provider, still seen; no inference_start, since none started.
|
|
287
|
+
this.emitTraceFn({
|
|
288
|
+
type: 'mcpl:inference_fail',
|
|
289
|
+
serverId,
|
|
290
|
+
model: null,
|
|
291
|
+
error: message,
|
|
292
|
+
streamed,
|
|
293
|
+
});
|
|
294
|
+
responder.respondError(-32603, message);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
241
297
|
|
|
242
298
|
// Emit trace: inference start
|
|
243
299
|
this.emitTraceFn({
|
|
@@ -263,12 +319,6 @@ export class InferenceRouter {
|
|
|
263
319
|
},
|
|
264
320
|
};
|
|
265
321
|
|
|
266
|
-
// Streaming needs a way to send chunks and an id to tag them with;
|
|
267
|
-
// without either, the request is answered whole.
|
|
268
|
-
const sendChunk = params.stream ? responder.sendChunk?.bind(responder) : undefined;
|
|
269
|
-
const chunkRequestId = sendChunk ? responder.requestId : undefined;
|
|
270
|
-
const streamed = chunkRequestId != null;
|
|
271
|
-
|
|
272
322
|
try {
|
|
273
323
|
// 5. Run inference. Both modes end in membrane's response, and one
|
|
274
324
|
// builder makes the result from it.
|
|
@@ -325,37 +375,32 @@ export class InferenceRouter {
|
|
|
325
375
|
* 2. Exact feature set match
|
|
326
376
|
* 3. Wildcard pattern match (first match wins)
|
|
327
377
|
* 4. Default from policy
|
|
328
|
-
*
|
|
378
|
+
*
|
|
379
|
+
* Only an entry naming a model counts: one that is empty or not a string
|
|
380
|
+
* names none and is passed over, as a missing one is. That includes what a
|
|
381
|
+
* server-sent conversation id or feature set such as `constructor` or
|
|
382
|
+
* `__proto__` finds on Object.prototype, which is never a string. A
|
|
383
|
+
* conversation id that is falsy reads no override.
|
|
384
|
+
*
|
|
385
|
+
* Undefined with no policy, or when none of these names a model: the
|
|
386
|
+
* request is then refused.
|
|
329
387
|
*/
|
|
330
388
|
private resolveModel(
|
|
331
389
|
featureSet: string,
|
|
332
390
|
conversationId?: string,
|
|
333
|
-
): string {
|
|
334
|
-
|
|
335
|
-
|
|
391
|
+
): string | undefined {
|
|
392
|
+
const policy = this.routingPolicy;
|
|
393
|
+
if (!policy) {
|
|
394
|
+
return undefined;
|
|
336
395
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return this.routingPolicy.byFeature[featureSet];
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Check wildcard patterns
|
|
349
|
-
if (this.routingPolicy.wildcards) {
|
|
350
|
-
for (const [pattern, matchedModel] of Object.entries(this.routingPolicy.wildcards)) {
|
|
351
|
-
if (wildcardMatch(pattern, featureSet)) {
|
|
352
|
-
return matchedModel;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Default
|
|
358
|
-
return this.routingPolicy.default;
|
|
396
|
+
const named = (value: unknown): string | undefined =>
|
|
397
|
+
typeof value === 'string' && value !== '' ? value : undefined;
|
|
398
|
+
|
|
399
|
+
return (conversationId ? named(policy.overrides?.[conversationId]) : undefined)
|
|
400
|
+
?? named(policy.byFeature?.[featureSet])
|
|
401
|
+
// First wildcard that matches and names a model.
|
|
402
|
+
?? Object.entries(policy.wildcards ?? {}).find(([pattern, m]) => named(m) && wildcardMatch(pattern, featureSet))?.[1]
|
|
403
|
+
?? named(policy.default);
|
|
359
404
|
}
|
|
360
405
|
|
|
361
406
|
// ==========================================================================
|
|
@@ -19,6 +19,13 @@ export interface ProsePrefix {
|
|
|
19
19
|
body: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/** Whether a `>>` (or `>>>`) prefix line reads `name` as a target: one token,
|
|
23
|
+
* and not `skip_reply`, which the line reads as private speech. For a name
|
|
24
|
+
* offered to an agent to write after `>>`. */
|
|
25
|
+
export function isProseTargetToken(name: string): boolean {
|
|
26
|
+
return /^\S+$/.test(name) && name !== 'skip_reply';
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
/** A REAL prefix line: `>>` immediately followed by a target token. */
|
|
23
30
|
export const PROSE_PREFIX_LINE = /^>>\S/;
|
|
24
31
|
|
|
@@ -641,6 +641,8 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
|
641
641
|
export function mcplPolicyGrants(config: McplServerConfig): string[] {
|
|
642
642
|
const grants: string[] = [];
|
|
643
643
|
if (isEffective(config.allowHostCommands)) grants.push('allowHostCommands');
|
|
644
|
+
// RFC-014 tool hints are an MCPL advertisement.
|
|
645
|
+
if (config.toolHints === true) grants.push('toolHints');
|
|
644
646
|
// An allow-list, except that a pattern matching a path denied by default
|
|
645
647
|
// grants it (computeGrant's explicitlyEnabled), named by the path.
|
|
646
648
|
const enabled = config.enabledCapabilities as unknown;
|