@animalabs/agent-framework 0.27.0 → 0.29.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 +145 -41
- package/dist/src/agent.d.ts +29 -2
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +73 -12
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server-core.d.ts.map +1 -1
- package/dist/src/api/mcp-server-core.js +3 -12
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +11 -5
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/auto-continue.d.ts +5 -2
- package/dist/src/auto-continue.d.ts.map +1 -1
- package/dist/src/auto-continue.js +9 -5
- package/dist/src/auto-continue.js.map +1 -1
- package/dist/src/code-execution/isolate/runner.d.ts +7 -5
- package/dist/src/code-execution/isolate/runner.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/runner.js +7 -5
- package/dist/src/code-execution/isolate/runner.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +3 -2
- 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 +468 -125
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +3651 -1111
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +133 -13
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +654 -59
- 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/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 +173 -2
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.d.ts +202 -0
- package/dist/src/heartbeat/heartbeat.d.ts.map +1 -0
- package/dist/src/heartbeat/heartbeat.js +778 -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/held-writes.d.ts +3 -3
- package/dist/src/held-writes.js +3 -3
- package/dist/src/host-presentation.d.ts +170 -0
- package/dist/src/host-presentation.d.ts.map +1 -0
- package/dist/src/host-presentation.js +561 -0
- package/dist/src/host-presentation.js.map +1 -0
- package/dist/src/index.d.ts +5 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/line-reader.d.ts +4 -0
- package/dist/src/line-reader.d.ts.map +1 -0
- package/dist/src/line-reader.js +59 -0
- package/dist/src/line-reader.js.map +1 -0
- 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 +34 -6
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +133 -54
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +2 -1
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts +1 -0
- package/dist/src/mcpl/convert-block.d.ts.map +1 -1
- package/dist/src/mcpl/convert-block.js +16 -4
- package/dist/src/mcpl/convert-block.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.js +4 -3
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +29 -11
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +43 -70
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +11 -7
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +3 -2
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +25 -3
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-handler.js +2 -2
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +12 -40
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +29 -23
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +18 -8
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +70 -30
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.d.ts +25 -0
- package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.js +25 -0
- package/dist/src/mcpl/skip-reply-wake.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-presence.d.ts +66 -14
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
- package/dist/src/mcpl/tool-presence.js +150 -80
- package/dist/src/mcpl/tool-presence.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts +10 -3
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +60 -30
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts +88 -55
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +220 -111
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts +27 -35
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +313 -91
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +1 -1
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +16 -16
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +31 -13
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/module-registry.d.ts +10 -8
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +15 -9
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/api/index.d.ts.map +1 -1
- package/dist/src/modules/api/index.js +4 -2
- package/dist/src/modules/api/index.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +33 -25
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +30 -17
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/sender-name.d.ts +7 -0
- package/dist/src/modules/sender-name.d.ts.map +1 -0
- package/dist/src/modules/sender-name.js +10 -0
- package/dist/src/modules/sender-name.js.map +1 -0
- package/dist/src/modules/workspace/answers.d.ts +26 -0
- package/dist/src/modules/workspace/answers.d.ts.map +1 -0
- package/dist/src/modules/workspace/answers.js +138 -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 +3 -0
- package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
- package/dist/src/modules/workspace/direct-write.js +59 -24
- package/dist/src/modules/workspace/direct-write.js.map +1 -1
- 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 +2 -1
- package/dist/src/modules/workspace/edit-text.d.ts.map +1 -1
- package/dist/src/modules/workspace/edit-text.js +4 -2
- package/dist/src/modules/workspace/edit-text.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts +4 -2
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +67 -50
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +63 -51
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +980 -547
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +125 -82
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +465 -241
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/read-page.d.ts.map +1 -1
- package/dist/src/modules/workspace/read-page.js +7 -1
- package/dist/src/modules/workspace/read-page.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +7 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +156 -50
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/modules/workspace/result-text.d.ts +1 -0
- package/dist/src/modules/workspace/result-text.d.ts.map +1 -1
- package/dist/src/modules/workspace/result-text.js.map +1 -1
- 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/types.d.ts +96 -43
- 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/notice-tags.d.ts +79 -0
- package/dist/src/notice-tags.d.ts.map +1 -0
- package/dist/src/notice-tags.js +83 -0
- package/dist/src/notice-tags.js.map +1 -0
- 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 +52 -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.js +1 -1
- package/dist/src/silent-heartbeat.d.ts +1 -53
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +56 -3
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/supplied-text.d.ts +35 -0
- package/dist/src/supplied-text.d.ts.map +1 -0
- package/dist/src/supplied-text.js +141 -0
- package/dist/src/supplied-text.js.map +1 -0
- package/dist/src/thinking-drops.d.ts +4 -11
- package/dist/src/thinking-drops.d.ts.map +1 -1
- package/dist/src/thinking-drops.js +7 -14
- package/dist/src/thinking-drops.js.map +1 -1
- 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 +39 -4
- package/dist/src/tool-args.js.map +1 -1
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +2 -1
- 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 +5 -4
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +8 -4
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +23 -11
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +3 -2
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +24 -26
- 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 +63 -18
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +18 -8
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +58 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +27 -13
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +34 -1
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/agent.ts +76 -12
- package/src/api/mcp-server-core.ts +3 -14
- package/src/api/server.ts +11 -5
- package/src/auto-continue.ts +12 -5
- package/src/code-execution/isolate/runner.ts +7 -5
- package/src/code-execution/isolate/scripts.ts +3 -2
- package/src/cut-off-round.ts +98 -0
- package/src/framework.ts +3772 -1138
- package/src/gate/event-gate.ts +705 -60
- package/src/gate/index.ts +9 -1
- package/src/gate/reply-judge.ts +512 -0
- package/src/gate/types.ts +176 -3
- package/src/heartbeat/heartbeat.ts +843 -0
- package/src/heartbeat/reminder-time.ts +107 -0
- package/src/held-writes.ts +3 -3
- package/src/host-presentation.ts +605 -0
- package/src/index.ts +5 -1
- package/src/line-reader.ts +58 -0
- package/src/mcpl/capability-mask.ts +3 -2
- package/src/mcpl/channel-registry.ts +139 -53
- package/src/mcpl/conversation-router.ts +2 -1
- package/src/mcpl/convert-block.ts +17 -4
- package/src/mcpl/hook-orchestrator.ts +4 -3
- package/src/mcpl/inbound-source.ts +45 -70
- package/src/mcpl/modern-connection.ts +11 -7
- package/src/mcpl/protocol-family.ts +24 -3
- package/src/mcpl/push-handler.ts +2 -2
- package/src/mcpl/references.ts +31 -23
- package/src/mcpl/server-connection.ts +76 -34
- package/src/mcpl/skip-reply-wake.ts +35 -0
- package/src/mcpl/tags.ts +13 -2
- package/src/mcpl/tool-presence.ts +149 -82
- package/src/mcpl/tool-signature.ts +64 -32
- package/src/mcpl/toolset-host.ts +212 -110
- package/src/mcpl/toolsets-module.ts +277 -91
- package/src/mcpl/transport.ts +18 -14
- package/src/mcpl/types.ts +32 -13
- package/src/module-registry.ts +14 -9
- package/src/modules/api/index.ts +4 -2
- package/src/modules/discord/index.ts +37 -25
- package/src/modules/history/index.ts +30 -18
- package/src/modules/sender-name.ts +9 -0
- package/src/modules/workspace/answers.ts +151 -0
- package/src/modules/workspace/branch-intent.ts +10 -5
- package/src/modules/workspace/direct-write.ts +59 -23
- package/src/modules/workspace/disk-agreement.ts +52 -21
- package/src/modules/workspace/edit-text.ts +7 -5
- package/src/modules/workspace/effects.ts +69 -50
- package/src/modules/workspace/index.ts +966 -556
- package/src/modules/workspace/observe.ts +497 -242
- package/src/modules/workspace/read-page.ts +7 -1
- package/src/modules/workspace/reconcile.ts +184 -49
- package/src/modules/workspace/result-text.ts +2 -0
- package/src/modules/workspace/sync.ts +16 -0
- package/src/modules/workspace/types.ts +96 -43
- package/src/modules/workspace/watcher.ts +8 -3
- package/src/notice-tags.ts +84 -0
- package/src/prose-drafts.ts +4 -3
- package/src/reasoning-loss-notice.ts +64 -0
- package/src/reasoning-loss.ts +120 -0
- package/src/send-tools.ts +3 -2
- package/src/sent-rows.ts +1 -1
- package/src/silent-heartbeat.ts +4 -3
- package/src/supplied-text.ts +154 -0
- package/src/thinking-drops.ts +10 -16
- package/src/tool-args.ts +38 -4
- package/src/tool-image-ledger.ts +2 -1
- package/src/tool-presentation.ts +5 -4
- package/src/tool-result-guard.ts +26 -11
- package/src/tune-out/coordinator.ts +25 -27
- package/src/tune-out/tools.ts +4 -1
- package/src/types/agent.ts +63 -18
- package/src/types/events.ts +18 -8
- package/src/types/framework.ts +59 -0
- package/src/types/module.ts +28 -13
- package/src/types/trace.ts +36 -1
package/src/agent.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { NOTICE_TAG } from './notice-tags.js';
|
|
2
|
+
import { heartbeatToolAlias } from './heartbeat/heartbeat.js';
|
|
1
3
|
import type { Membrane, NormalizedMessage, NormalizedRequest, ContentBlock, ThinkingBindingEntry, YieldingStream } from '@animalabs/membrane';
|
|
2
4
|
import { isAbortedResponse } from '@animalabs/membrane';
|
|
3
5
|
import type { CacheWireReceipt, KvUnifiedRequestHooks } from './kv-unified-wire.js';
|
|
@@ -97,10 +99,15 @@ export interface StartStreamResult {
|
|
|
97
99
|
/** The compile's `thinkingRecordedAfter`, when it gave one: a reply stored at or below it predates the branch's
|
|
98
100
|
* thinking-binding record, so the provider may drop its thinking with no strip having missed. */
|
|
99
101
|
thinkingRecordedAfter?: number;
|
|
102
|
+
/** The key (`branchKey`) of the branch the compile read, when its provenance names one. */
|
|
103
|
+
thinkingBranch?: string;
|
|
100
104
|
/** Tells the context manager what the stream's later rounds carried after the compiled messages, every round so
|
|
101
105
|
* far in sent order, with this request's head (context-manager's `noteRequestSent`). Present only where the
|
|
102
106
|
* model binds its thinking and the context manager keeps a record it can be told of. */
|
|
103
107
|
noteRequestSent?: (rows: readonly SentRow[]) => boolean;
|
|
108
|
+
/** The stored bodies the request's compile carried, by each body's first message id (its provenance's raw bodies).
|
|
109
|
+
* Absent when the compile reports no provenance. */
|
|
110
|
+
carriedBodies?: ReadonlySet<MessageId>;
|
|
104
111
|
}
|
|
105
112
|
import type {
|
|
106
113
|
ContextManager,
|
|
@@ -112,6 +119,7 @@ import type {
|
|
|
112
119
|
MessageId,
|
|
113
120
|
MessageMetadata,
|
|
114
121
|
RequestIdentity,
|
|
122
|
+
ThinkingStrip,
|
|
115
123
|
} from '@animalabs/context-manager';
|
|
116
124
|
import type {
|
|
117
125
|
AgentConfig,
|
|
@@ -257,8 +265,7 @@ export function resolveAutoContinue(config: AgentConfig): { maxConsecutive: numb
|
|
|
257
265
|
return { maxConsecutive: max };
|
|
258
266
|
}
|
|
259
267
|
|
|
260
|
-
|
|
261
|
-
function branchKey(branch: { readonly id: string; readonly created: number }): string {
|
|
268
|
+
export function branchKey(branch: { readonly id: string; readonly created: number }): string {
|
|
262
269
|
return `${branch.id}\u0000${branch.created}`;
|
|
263
270
|
}
|
|
264
271
|
|
|
@@ -493,7 +500,13 @@ export class Agent {
|
|
|
493
500
|
if (this.allowedTools === 'all') {
|
|
494
501
|
return true;
|
|
495
502
|
}
|
|
496
|
-
|
|
503
|
+
if (this.allowedTools.includes(toolName)) return true;
|
|
504
|
+
// A heartbeat tool is one tool under either prefix: the framework's
|
|
505
|
+
// `heartbeat--x` and heartbeat-mcpl's `mcpl--heartbeat--x`, which an
|
|
506
|
+
// allowedTools written for the server names. Only one of them is ever
|
|
507
|
+
// offered, since the framework refuses the server beside its heartbeat.
|
|
508
|
+
const alias = heartbeatToolAlias(toolName);
|
|
509
|
+
return alias !== undefined && this.allowedTools.includes(alias);
|
|
497
510
|
}
|
|
498
511
|
|
|
499
512
|
/**
|
|
@@ -1176,8 +1189,9 @@ export class Agent {
|
|
|
1176
1189
|
// Tolerates partial context-manager doubles (tests build Agents over
|
|
1177
1190
|
// stubs). Without a watermark, a reader that looks for one takes its own
|
|
1178
1191
|
// side: the coalescer counts the message as read, and the framework's
|
|
1179
|
-
// unreadNoticesForPrimary counts it as unread. A
|
|
1180
|
-
// reads the answered watermark instead
|
|
1192
|
+
// unreadNoticesForPrimary counts it as unread. A wake for a deferred
|
|
1193
|
+
// write or a stored message reads the answered watermark instead
|
|
1194
|
+
// (deferredWriteDone, storedMessageDone).
|
|
1181
1195
|
this.consumedWatermark = this.pendingWatermark();
|
|
1182
1196
|
this.watermarkMarks++;
|
|
1183
1197
|
this.shownWatermark = this.consumedWatermark;
|
|
@@ -1225,6 +1239,21 @@ export class Agent {
|
|
|
1225
1239
|
this.watermarkMarks++;
|
|
1226
1240
|
}
|
|
1227
1241
|
|
|
1242
|
+
/**
|
|
1243
|
+
* The branch now (`branchKey`) and its head, as `pendingWatermark` reads it: a message stored on that branch later
|
|
1244
|
+
* has a larger sequence. Sequences are numbered per branch, so one compares with another only on the same branch.
|
|
1245
|
+
*/
|
|
1246
|
+
branchHead(): { branch: string; sequence: number } | undefined {
|
|
1247
|
+
const head = this.pendingWatermark();
|
|
1248
|
+
const cm = this.contextManager as Partial<ContextManager>;
|
|
1249
|
+
if (!head || typeof cm.currentBranchRef !== 'function') return undefined;
|
|
1250
|
+
try {
|
|
1251
|
+
return { branch: branchKey(cm.currentBranchRef()), sequence: head.sequence };
|
|
1252
|
+
} catch {
|
|
1253
|
+
return undefined;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1228
1257
|
/** The watermark a successful compile will establish (taken before, applied after). */
|
|
1229
1258
|
private pendingWatermark(): { branch: string; sequence: number } | null {
|
|
1230
1259
|
const cm = this.contextManager as Partial<ContextManager>;
|
|
@@ -1313,6 +1342,15 @@ export class Agent {
|
|
|
1313
1342
|
*/
|
|
1314
1343
|
onCompiledToSend?: (messages: readonly unknown[]) => void;
|
|
1315
1344
|
|
|
1345
|
+
/**
|
|
1346
|
+
* A host's hook, called with each committing compile's `thinkingStrip` (never a dry run's) and the key of the
|
|
1347
|
+
* branch it read (`branchKey`), as the compile returns, whether or not its request is then sent, since the
|
|
1348
|
+
* context manager's binding record takes the strip either way. One that throws is logged. The framework sets it
|
|
1349
|
+
* at the agent's first stream unless a host has set one, so a host's own observer stands in for the framework's,
|
|
1350
|
+
* whose `[reasoning]` line for the agent then isn't told.
|
|
1351
|
+
*/
|
|
1352
|
+
onThinkingStrip?: (strip: ThinkingStrip, branch: string) => void;
|
|
1353
|
+
|
|
1316
1354
|
/**
|
|
1317
1355
|
* The newest content of `namespace` among this agent's rows stored since
|
|
1318
1356
|
* its last compile built to send (unsentSince), and whether every part of
|
|
@@ -1516,6 +1554,13 @@ export class Agent {
|
|
|
1516
1554
|
throw error;
|
|
1517
1555
|
}
|
|
1518
1556
|
if (watermark) this.applyCompileWatermark(watermark, marksBefore);
|
|
1557
|
+
if (!opts?.dryRun && result.thinkingStrip && this.onThinkingStrip) {
|
|
1558
|
+
try {
|
|
1559
|
+
this.onThinkingStrip(result.thinkingStrip, result.provenance ? branchKey(result.provenance.branch) : '');
|
|
1560
|
+
} catch (err) {
|
|
1561
|
+
console.error(`[agent] ${this.name}: the host's thinking-strip observer failed:`, err);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1519
1564
|
return result;
|
|
1520
1565
|
}
|
|
1521
1566
|
|
|
@@ -2064,10 +2109,10 @@ export class Agent {
|
|
|
2064
2109
|
// ran. The stream driver collects it on that stream's terminal event, or
|
|
2065
2110
|
// at the loop's end when the iterator closed without one, while the
|
|
2066
2111
|
// stream still owns the agent. This clear is for the records no driver
|
|
2067
|
-
// collects: a
|
|
2068
|
-
//
|
|
2069
|
-
//
|
|
2070
|
-
//
|
|
2112
|
+
// collects: a cancel of a stream no driver iterates; one whose stream's
|
|
2113
|
+
// terminal came after another stream took the agent; or a cancel after
|
|
2114
|
+
// the stream's complete event. A fresh stream must not inherit one and
|
|
2115
|
+
// read its own later error as a deliberate stop.
|
|
2071
2116
|
this._pendingCancel = undefined;
|
|
2072
2117
|
this._inferenceStartedAt = Date.now();
|
|
2073
2118
|
this.lastStreamInputTokens = 0;
|
|
@@ -2146,7 +2191,11 @@ export class Agent {
|
|
|
2146
2191
|
// Read against the messages the stream sends, which membrane's report indexes (RoundReport's coordinates).
|
|
2147
2192
|
placeDrops: (entries) => placeThinkingDrops(entries, request.messages, compiled),
|
|
2148
2193
|
...this.requestNotes(binding, compiled),
|
|
2194
|
+
...(compiled.provenance
|
|
2195
|
+
? { carriedBodies: new Set(compiled.provenance.messages.flatMap((m) => m.kind === 'raw' ? m.bodies.map((b) => b.messageId) : [])) }
|
|
2196
|
+
: {}),
|
|
2149
2197
|
...(compiled.recordedAfter !== undefined ? { thinkingRecordedAfter: compiled.recordedAfter } : {}),
|
|
2198
|
+
...(compiled.provenance ? { thinkingBranch: branchKey(compiled.provenance.branch) } : {}),
|
|
2150
2199
|
...(kvEnabled
|
|
2151
2200
|
? {
|
|
2152
2201
|
takeKvSubmission: () => kvQueue.shift(),
|
|
@@ -2261,12 +2310,24 @@ export class Agent {
|
|
|
2261
2310
|
* one is still ahead; otherwise the next start drops it. `reason` is
|
|
2262
2311
|
* provenance for the framework's inference:aborted trace and marker
|
|
2263
2312
|
* metadata (e.g. 'zombie_reclaim', 'subagent_cancel'); it never reaches
|
|
2264
|
-
* the provider.
|
|
2313
|
+
* the provider. When the stream's cancel() throws, the throw goes on to
|
|
2314
|
+
* the caller, and the agent still holds the stream, its cancel record as
|
|
2315
|
+
* it was before the call.
|
|
2265
2316
|
*/
|
|
2266
2317
|
cancelStream(reason?: string): void {
|
|
2267
2318
|
if ('stream' in this._state && this._state.stream) {
|
|
2319
|
+
// A cancel that throws leaves the stream going and the agent holding
|
|
2320
|
+
// it, so this call's record is taken back, leaving any earlier one: a
|
|
2321
|
+
// later end of that stream, an `error` the provider reports included,
|
|
2322
|
+
// is not read as this stop's.
|
|
2323
|
+
const earlier = this._pendingCancel;
|
|
2268
2324
|
this._pendingCancel = { reason };
|
|
2269
|
-
|
|
2325
|
+
try {
|
|
2326
|
+
this._state.stream.cancel();
|
|
2327
|
+
} catch (error) {
|
|
2328
|
+
this._pendingCancel = earlier;
|
|
2329
|
+
throw error;
|
|
2330
|
+
}
|
|
2270
2331
|
} else {
|
|
2271
2332
|
this._stopWithoutStream = { reason };
|
|
2272
2333
|
}
|
|
@@ -2338,7 +2399,9 @@ export class Agent {
|
|
|
2338
2399
|
}
|
|
2339
2400
|
|
|
2340
2401
|
const injectedText = systemInjections.map((block) =>
|
|
2341
|
-
block.type === 'text'
|
|
2402
|
+
block.type === 'text'
|
|
2403
|
+
? block.text
|
|
2404
|
+
: `[${NOTICE_TAG.blockNotShown}: ${/^[aeiou]/i.test(block.type) ? 'an' : 'a'} ${block.type} block; the system prompt carries text only.]`);
|
|
2342
2405
|
|
|
2343
2406
|
return this.systemPrompt + '\n' + injectedText.join('\n');
|
|
2344
2407
|
}
|
|
@@ -2442,6 +2505,7 @@ export class Agent {
|
|
|
2442
2505
|
id: block.id,
|
|
2443
2506
|
name: block.name,
|
|
2444
2507
|
input: block.input,
|
|
2508
|
+
...(block.unparseableInput !== undefined ? { unparseableInput: block.unparseableInput } : {}),
|
|
2445
2509
|
});
|
|
2446
2510
|
} else if (block.type === 'text') {
|
|
2447
2511
|
speechContent.push(block);
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import WebSocket from 'ws';
|
|
15
|
-
import {
|
|
15
|
+
import { escapeLineSeparators, readLines } from '../line-reader.js';
|
|
16
16
|
import type { ApiRequest, ApiResponse, ApiEvent } from './types.js';
|
|
17
17
|
|
|
18
18
|
// ============================================================================
|
|
@@ -86,18 +86,7 @@ export class McpServer {
|
|
|
86
86
|
// Connect to WebSocket API
|
|
87
87
|
await this.connectWs();
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
const rl = createInterface({
|
|
91
|
-
input: process.stdin,
|
|
92
|
-
output: process.stdout,
|
|
93
|
-
terminal: false,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
rl.on('line', (line) => {
|
|
97
|
-
this.handleInput(line);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
rl.on('close', () => {
|
|
89
|
+
readLines(process.stdin, (line) => this.handleInput(line), () => {
|
|
101
90
|
this.close();
|
|
102
91
|
process.exit(0);
|
|
103
92
|
});
|
|
@@ -667,7 +656,7 @@ export class McpServer {
|
|
|
667
656
|
}
|
|
668
657
|
|
|
669
658
|
private send(message: JsonRpcResponse | JsonRpcNotification): void {
|
|
670
|
-
process.stdout.write(JSON.stringify(message) + '\n');
|
|
659
|
+
process.stdout.write(escapeLineSeparators(JSON.stringify(message)) + '\n');
|
|
671
660
|
}
|
|
672
661
|
|
|
673
662
|
close(): void {
|
package/src/api/server.ts
CHANGED
|
@@ -489,9 +489,13 @@ export class ApiServer {
|
|
|
489
489
|
if (!params.participant || !params.content) {
|
|
490
490
|
throw new Error('participant and content are required');
|
|
491
491
|
}
|
|
492
|
-
// ApiModule stores the message as one text
|
|
493
|
-
// refuses unless its text is a string
|
|
494
|
-
//
|
|
492
|
+
// ApiModule stores the participant as a name, and the message as one text
|
|
493
|
+
// block, which context-manager refuses unless its text is a string. So a
|
|
494
|
+
// participant or content that isn't text is refused here, where the
|
|
495
|
+
// caller hears it, rather than at dispatch.
|
|
496
|
+
if (typeof params.participant !== 'string') {
|
|
497
|
+
throw new Error('participant must be text');
|
|
498
|
+
}
|
|
495
499
|
if (typeof params.content !== 'string') {
|
|
496
500
|
throw new Error('content must be text');
|
|
497
501
|
}
|
|
@@ -649,7 +653,8 @@ export class ApiServer {
|
|
|
649
653
|
throw new Error('agentName is required');
|
|
650
654
|
}
|
|
651
655
|
|
|
652
|
-
const result = this.framework.undoLastTurn(params.agentName);
|
|
656
|
+
const { settled, ...result } = this.framework.undoLastTurn(params.agentName);
|
|
657
|
+
await settled;
|
|
653
658
|
|
|
654
659
|
if (result.undone) {
|
|
655
660
|
this.broadcast('branch:switched', {
|
|
@@ -667,7 +672,8 @@ export class ApiServer {
|
|
|
667
672
|
throw new Error('agentName is required');
|
|
668
673
|
}
|
|
669
674
|
|
|
670
|
-
const result = this.framework.redo(params.agentName);
|
|
675
|
+
const { settled, ...result } = this.framework.redo(params.agentName);
|
|
676
|
+
await settled;
|
|
671
677
|
|
|
672
678
|
if (result.redone) {
|
|
673
679
|
this.broadcast('branch:switched', {
|
package/src/auto-continue.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* row the next turn reads.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { NOTICE_TAG } from './notice-tags.js';
|
|
9
|
+
|
|
8
10
|
/** The reason on a continuation's wake (InferenceRequest.reason). */
|
|
9
11
|
export const AUTO_CONTINUE_REASON = 'auto-continue';
|
|
10
12
|
|
|
@@ -21,8 +23,9 @@ export const AUTO_CONTINUE_STOP_REASONS: ReadonlySet<string> = new Set(['end_tur
|
|
|
21
23
|
* one shared message slot), says another turn starts, says how to stop
|
|
22
24
|
* (`stop`, an imperative such as "call skip_reply") and that anything written
|
|
23
25
|
* without it is sent as usual, and, on the last continuation of a run, that
|
|
24
|
-
* the agent waits for the next event after it. When the last
|
|
25
|
-
* words were held because that turn
|
|
26
|
+
* the agent waits for the next event after it. When some or all of the last
|
|
27
|
+
* turn's closing words were held because a send that turn may already have
|
|
28
|
+
* posted where they were going, it says so. Short,
|
|
26
29
|
* neutral and event-shaped on purpose: it rides as a system row in the
|
|
27
30
|
* window, where instruction blocks about output have tripped classifiers,
|
|
28
31
|
* and wording that reads as a lapse or a budget invites a published reply.
|
|
@@ -32,11 +35,15 @@ export function autoContinueNotice(opts: {
|
|
|
32
35
|
stop: string;
|
|
33
36
|
last: boolean;
|
|
34
37
|
heldAfterSend?: boolean;
|
|
38
|
+
/** The run began with a heartbeat's turn, whose plain speech is held as drafts. */
|
|
39
|
+
heartbeat?: boolean;
|
|
35
40
|
}): string {
|
|
36
|
-
return `[continue] ${opts.agentName}, another turn starts now.` +
|
|
41
|
+
return `[${NOTICE_TAG.continue}] ${opts.agentName}, another turn starts now.` +
|
|
37
42
|
(opts.heldAfterSend
|
|
38
|
-
? '
|
|
43
|
+
? ' Some or all of your closing words last turn were held, not sent: a send that turn may already have posted where they were going.'
|
|
39
44
|
: '') +
|
|
40
|
-
|
|
45
|
+
(opts.heartbeat
|
|
46
|
+
? ` If you're done, ${opts.stop}; plain words you write without it and with no destination are held as drafts, as on the heartbeat's turn, and an explicit send reaches a channel.`
|
|
47
|
+
: ` If you're done, ${opts.stop}; anything you write without it is sent as usual.`) +
|
|
41
48
|
(opts.last ? ' This is the last continuation in a row: after this turn you wait for the next event either way.' : '');
|
|
42
49
|
}
|
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
* which ends with it, so nothing a script held stays after it.
|
|
5
5
|
*
|
|
6
6
|
* Every frame from the child is decoded and checked before the parent acts on
|
|
7
|
-
* it, since the child is untrusted. A frame that breaks the protocol, a
|
|
8
|
-
* that exits without saying how the script ended
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* call
|
|
7
|
+
* it, since the child is untrusted. A frame that breaks the protocol, or a
|
|
8
|
+
* child that exits without saying how the script ended when no stop was
|
|
9
|
+
* asked, ends the run as crashed. One that doesn't stop when asked is ended
|
|
10
|
+
* once the stop's grace has passed, and the run ends stopped, marked short.
|
|
11
|
+
* A tool call goes to `onToolCall`, the bridge to the framework's dispatcher,
|
|
12
|
+
* and its outcome goes into the run's call record at dispatch, as running
|
|
13
|
+
* until it's answered.
|
|
12
14
|
*
|
|
13
15
|
* What the host holds for a script is bounded: at most MAX_CALLS_IN_FLIGHT of
|
|
14
16
|
* its calls wait for answers at once, and at most MAX_CALLS_IN_FLIGHT are out
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* dispatcher a script's calls go through, and how to wake an agent.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
+
import { NOTICE_TAG } from '../../notice-tags.js';
|
|
29
30
|
import { ChildCapacity, ScriptRun, type CallAnswer, type Ending, type ScriptRunOptions, type ToolAnswer } from './runner.js';
|
|
30
31
|
import type { CallSummary } from './calls.js';
|
|
31
32
|
import type { OutputBounds, OutputWindow } from './output.js';
|
|
@@ -478,10 +479,10 @@ function leftEndingText(scriptId: string, ending: Ending): string {
|
|
|
478
479
|
: ending.how === 'stopped'
|
|
479
480
|
? ending.why ? `was stopped: ${ending.why}` : 'was stopped at its limit'
|
|
480
481
|
: 'ended';
|
|
481
|
-
return `[script ${scriptId}] Your script ${how}. {"action": "wait", "script_id": "${scriptId}"} gives its result.`;
|
|
482
|
+
return `[${NOTICE_TAG.script} ${scriptId}] Your script ${how}. {"action": "wait", "script_id": "${scriptId}"} gives its result.`;
|
|
482
483
|
}
|
|
483
484
|
|
|
484
485
|
function wakeText(scriptId: string, payloadJson: string, n: number, max: number, ageMs: number): string {
|
|
485
486
|
const minutes = Math.round(ageMs / 60_000);
|
|
486
|
-
return `[script ${scriptId}] Woke you with wake_agent(), ${minutes} min after it started (wake ${n} of ${max}): ${payloadJson}`;
|
|
487
|
+
return `[${NOTICE_TAG.script} ${scriptId}] Woke you with wake_agent(), ${minutes} min after it started (wake ${n} of ${max}): ${payloadJson}`;
|
|
487
488
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import type { ContentBlock, NormalizedResponse } from '@animalabs/membrane';
|
|
3
|
+
|
|
4
|
+
export interface CutOffCall {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
input: Record<string, unknown>;
|
|
8
|
+
unparseableInput?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CutOffRound {
|
|
12
|
+
content: ContentBlock[];
|
|
13
|
+
start: number;
|
|
14
|
+
calls: CutOffCall[];
|
|
15
|
+
unnamed: boolean;
|
|
16
|
+
xml: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The last round of a response that ended at the output limit, when it holds
|
|
21
|
+
* calls that never ran, or, in XML tool mode, a tool-call block cut off before
|
|
22
|
+
* any call in it was named (`unnamed`). Membrane yields a round's calls only
|
|
23
|
+
* at its tool_use stop, or in XML tool mode at its `</function_calls>` stop,
|
|
24
|
+
* and the round begins after the last result that answers one of the
|
|
25
|
+
* response's calls.
|
|
26
|
+
*/
|
|
27
|
+
export function cutOffRound(response: NormalizedResponse): CutOffRound | undefined {
|
|
28
|
+
if (response.stopReason !== 'max_tokens') return undefined;
|
|
29
|
+
const callIds = new Set<string>();
|
|
30
|
+
let start = 0;
|
|
31
|
+
response.content.forEach((block, index) => {
|
|
32
|
+
if (block.type === 'tool_use') callIds.add(block.id);
|
|
33
|
+
else if (block.type === 'tool_result' && callIds.has(block.toolUseId)) start = index + 1;
|
|
34
|
+
});
|
|
35
|
+
const cut = response.details?.stop?.unclosedToolBlock ? cutToolBlock(response.content, start) : undefined;
|
|
36
|
+
const content = cut ?? response.content;
|
|
37
|
+
const uses = content.slice(start).filter((block): block is ContentBlock & { type: 'tool_use' } => block.type === 'tool_use');
|
|
38
|
+
const calls = uses.map(({ id, name, input, unparseableInput }) => ({
|
|
39
|
+
id, name, input,
|
|
40
|
+
...(unparseableInput !== undefined ? { unparseableInput } : {}),
|
|
41
|
+
}));
|
|
42
|
+
const unnamed = !!cut && calls.length === 0;
|
|
43
|
+
const xml = !!cut || uses.some((block) => (block as { rawXml?: string }).rawXml !== undefined);
|
|
44
|
+
return calls.length > 0 || unnamed ? { content, start, calls, unnamed, xml } : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Membrane 0.8.0's grammar (unchanged from 0.7.2) for the anthropic-xml
|
|
49
|
+
* formatter's XML tool mode. Membrane exports two of its predicates,
|
|
50
|
+
* hasUnclosedToolBlock and endsWithPartialToolBlock, but not the grammar
|
|
51
|
+
* itself (dist/utils/tool-parser.js, INVOKE_REGEX): a block opens
|
|
52
|
+
* with `<function_calls>` and each call with `<invoke name="…">`, either tag
|
|
53
|
+
* optionally `antml:` prefixed, and a closer ends the innermost block still
|
|
54
|
+
* open. Its parser takes spans out of an unclosed block's text, which leaves
|
|
55
|
+
* that text in several text blocks: thinking, which stays with the round, a
|
|
56
|
+
* whole inner block its stop sequence didn't end, whose calls are the round's
|
|
57
|
+
* too, and results, which don't stay. Only a block's opener
|
|
58
|
+
* and each call's name head are read, nothing after a name, and a name that
|
|
59
|
+
* isn't a tool name's characters isn't taken as one: the formatter writes a
|
|
60
|
+
* stored call's name into the next request as it is.
|
|
61
|
+
*/
|
|
62
|
+
const OPENER = /<(antml:)?function_calls(>|[^>]*$)/g;
|
|
63
|
+
const CLOSER = /<\/(antml:)?function_calls>/g;
|
|
64
|
+
const NAME_HEAD = /<(antml:)?invoke\s+name=(["'])((?:(?!\2).)+)\2\s*\/?>/g;
|
|
65
|
+
const TOOL_NAME = /^[A-Za-z0-9_.:-]{1,128}$/;
|
|
66
|
+
|
|
67
|
+
function cutToolBlock(content: ContentBlock[], start: number): ContentBlock[] | undefined {
|
|
68
|
+
const texts = content.map((block, index) => ({ block, index }))
|
|
69
|
+
.filter((entry): entry is { block: ContentBlock & { type: 'text' }; index: number } => entry.index >= start && entry.block.type === 'text');
|
|
70
|
+
const joined = texts.map(({ block }) => block.text).join('');
|
|
71
|
+
const tags = [
|
|
72
|
+
...[...joined.matchAll(OPENER)].map((match) => ({ index: match.index, opens: true })),
|
|
73
|
+
...[...joined.matchAll(CLOSER)].map((match) => ({ index: match.index, opens: false })),
|
|
74
|
+
].sort((a, b) => a.index - b.index);
|
|
75
|
+
const open: number[] = [];
|
|
76
|
+
for (const tag of tags) {
|
|
77
|
+
if (tag.opens) open.push(tag.index);
|
|
78
|
+
else open.pop();
|
|
79
|
+
}
|
|
80
|
+
if (open.length === 0) return undefined;
|
|
81
|
+
const opener = open[0]!;
|
|
82
|
+
let at = opener;
|
|
83
|
+
const holder = texts.find(({ block }) => {
|
|
84
|
+
if (at < block.text.length) return true;
|
|
85
|
+
at -= block.text.length;
|
|
86
|
+
return false;
|
|
87
|
+
})!;
|
|
88
|
+
const blockText = joined.slice(opener);
|
|
89
|
+
const heads = [...blockText.matchAll(NAME_HEAD)].filter((head) => TOOL_NAME.test(head[3]!));
|
|
90
|
+
const calls: ContentBlock[] = heads.map((head, index) => ({
|
|
91
|
+
type: 'tool_use', id: `tool_${randomUUID()}`, name: head[3]!, input: {},
|
|
92
|
+
unparseableInput: blockText.slice(head.index, heads[index + 1]?.index),
|
|
93
|
+
}));
|
|
94
|
+
const prose: ContentBlock[] = at > 0 ? [{ type: 'text', text: holder.block.text.slice(0, at) }] : [];
|
|
95
|
+
const kept = content.slice(holder.index + 1)
|
|
96
|
+
.filter((block) => block.type === 'thinking' || block.type === 'redacted_thinking' || block.type === 'tool_use');
|
|
97
|
+
return [...content.slice(0, holder.index), ...prose, ...kept, ...calls];
|
|
98
|
+
}
|