@animalabs/agent-framework 0.22.3 → 0.22.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/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +7 -1
- package/dist/src/agent.js.map +1 -1
- package/dist/src/framework.d.ts +9 -0
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +55 -6
- package/dist/src/framework.js.map +1 -1
- package/dist/src/thinking-token-stamp.d.ts +12 -0
- package/dist/src/thinking-token-stamp.d.ts.map +1 -1
- package/dist/src/thinking-token-stamp.js +34 -3
- package/dist/src/thinking-token-stamp.js.map +1 -1
- package/package.json +4 -1
- package/src/agent.ts +7 -1
- package/src/framework.ts +58 -6
- package/src/thinking-token-stamp.ts +34 -3
|
@@ -4,4 +4,16 @@ import type { ContentBlock } from '@animalabs/membrane';
|
|
|
4
4
|
* set, every other block is the same object.
|
|
5
5
|
*/
|
|
6
6
|
export declare function stampThinkingTokenEstimates(blocks: ContentBlock[], outputTokens: number): ContentBlock[];
|
|
7
|
+
/**
|
|
8
|
+
* A request's messages with no top-level block carrying `tokenEstimate`. Both
|
|
9
|
+
* of an agent's request builders call it on the compiled messages: a
|
|
10
|
+
* framework turn's (`Agent.assembleActivationRequest`) and the direct API's
|
|
11
|
+
* (`Agent.runInferenceWithInjections`). A block that carries the stamp is
|
|
12
|
+
* copied without it, and so is the message that holds it. Every other
|
|
13
|
+
* message and block is passed on as it is, and nothing is changed in place,
|
|
14
|
+
* so the stored block keeps its stamp.
|
|
15
|
+
*/
|
|
16
|
+
export declare function withoutTokenEstimates<T extends {
|
|
17
|
+
content: ContentBlock[];
|
|
18
|
+
}>(messages: T[]): T[];
|
|
7
19
|
//# sourceMappingURL=thinking-token-stamp.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thinking-token-stamp.d.ts","sourceRoot":"","sources":["../../src/thinking-token-stamp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"thinking-token-stamp.d.ts","sourceRoot":"","sources":["../../src/thinking-token-stamp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAqExD;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,YAAY,EAAE,MAAM,GAAG,YAAY,EAAE,CAyBxG;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS;IAAE,OAAO,EAAE,YAAY,EAAE,CAAA;CAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAY/F"}
|
|
@@ -24,9 +24,13 @@
|
|
|
24
24
|
* the hidden thinking is the bulk of the output, and this stamp is what makes
|
|
25
25
|
* the compiled budget honest (see context-manager `SIGNATURE_CHARS_PER_TOKEN`).
|
|
26
26
|
*
|
|
27
|
-
* Wire safety:
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* Wire safety: the stamp is context-manager's pricing metadata, and no field
|
|
28
|
+
* a provider takes. The Messages API refuses a block that carries it
|
|
29
|
+
* ("Extra inputs are not permitted"), and membrane (0.5.87 and 0.6.0) sends a
|
|
30
|
+
* `redacted_thinking` block to Anthropic and Bedrock with every field it has.
|
|
31
|
+
* So an agent's request builders drop the stamp from the compiled messages
|
|
32
|
+
* (`withoutTokenEstimates`), and the stored block, which context-manager
|
|
33
|
+
* prices, keeps it. The signature itself is copied, never edited.
|
|
30
34
|
*/
|
|
31
35
|
const PROSE_CHARS_PER_TOKEN = 2.9;
|
|
32
36
|
const DENSE_CHARS_PER_TOKEN = 2.3;
|
|
@@ -100,4 +104,31 @@ export function stampThinkingTokenEstimates(blocks, outputTokens) {
|
|
|
100
104
|
return { ...block, tokenEstimate };
|
|
101
105
|
});
|
|
102
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* A request's messages with no top-level block carrying `tokenEstimate`. Both
|
|
109
|
+
* of an agent's request builders call it on the compiled messages: a
|
|
110
|
+
* framework turn's (`Agent.assembleActivationRequest`) and the direct API's
|
|
111
|
+
* (`Agent.runInferenceWithInjections`). A block that carries the stamp is
|
|
112
|
+
* copied without it, and so is the message that holds it. Every other
|
|
113
|
+
* message and block is passed on as it is, and nothing is changed in place,
|
|
114
|
+
* so the stored block keeps its stamp.
|
|
115
|
+
*/
|
|
116
|
+
export function withoutTokenEstimates(messages) {
|
|
117
|
+
return messages.map((message) => {
|
|
118
|
+
if (!message.content.some(isStamped))
|
|
119
|
+
return message;
|
|
120
|
+
return {
|
|
121
|
+
...message,
|
|
122
|
+
content: message.content.map((block) => {
|
|
123
|
+
if (!isStamped(block))
|
|
124
|
+
return block;
|
|
125
|
+
const { tokenEstimate: _stamp, ...unstamped } = block;
|
|
126
|
+
return unstamped;
|
|
127
|
+
}),
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function isStamped(block) {
|
|
132
|
+
return typeof block === 'object' && block !== null && 'tokenEstimate' in block;
|
|
133
|
+
}
|
|
103
134
|
//# sourceMappingURL=thinking-token-stamp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thinking-token-stamp.js","sourceRoot":"","sources":["../../src/thinking-token-stamp.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"thinking-token-stamp.js","sourceRoot":"","sources":["../../src/thinking-token-stamp.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@animalabs/agent-framework",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.5",
|
|
4
4
|
"description": "Multi-agent framework with pluggable modules, persistent state, and concurrent tool execution",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/anima-research/agent-framework.git"
|
|
8
8
|
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"tag": "release-0.22"
|
|
11
|
+
},
|
|
9
12
|
"type": "module",
|
|
10
13
|
"main": "dist/src/index.js",
|
|
11
14
|
"types": "dist/src/index.d.ts",
|
package/src/agent.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { isAbortedResponse } from '@animalabs/membrane';
|
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import type { CacheWireReceipt, KvUnifiedRequestHooks } from './kv-unified-wire.js';
|
|
5
5
|
import { ToolResultGuard } from './tool-result-guard.js';
|
|
6
|
+
import { withoutTokenEstimates } from './thinking-token-stamp.js';
|
|
6
7
|
import { assertBooleanSetting, holdsSettings } from './config-checks.js';
|
|
7
8
|
import {
|
|
8
9
|
indexSilentTickRows,
|
|
@@ -1088,8 +1089,11 @@ export class Agent {
|
|
|
1088
1089
|
// notice.
|
|
1089
1090
|
let unguardedResults: ReturnType<Agent['buildToolResultMessages']> | undefined;
|
|
1090
1091
|
const assemble = async (): Promise<NormalizedRequest> => {
|
|
1091
|
-
const
|
|
1092
|
+
const compiled = await this.toolResultGuard.afterAnnotations(
|
|
1092
1093
|
() => this.compileWithInjections(budget, injections));
|
|
1094
|
+
const { systemInjections } = compiled;
|
|
1095
|
+
// The framework's own pricing stamp never goes out (thinking-token-stamp.ts).
|
|
1096
|
+
const messages = withoutTokenEstimates(compiled.messages);
|
|
1093
1097
|
// Read once, after the first compile, since results may have become
|
|
1094
1098
|
// ready while it ran; the refusal retry, assembled while the agent is
|
|
1095
1099
|
// inferring, sends the same ones.
|
|
@@ -1285,6 +1289,8 @@ export class Agent {
|
|
|
1285
1289
|
),
|
|
1286
1290
|
}))
|
|
1287
1291
|
.filter((m) => m.content.length > 0);
|
|
1292
|
+
// The framework's own pricing stamp never goes out (thinking-token-stamp.ts).
|
|
1293
|
+
messages = withoutTokenEstimates(messages);
|
|
1288
1294
|
|
|
1289
1295
|
// Silent heartbeat ticks store no prompt row. Render a request-only
|
|
1290
1296
|
// separator before each tick's first surviving row so the wire formatter
|
package/src/framework.ts
CHANGED
|
@@ -238,6 +238,29 @@ const silenceCauseOf = (calls: Array<{ name: string; input?: unknown }>): 'send'
|
|
|
238
238
|
return calls.some(isSilencingCall) ? 'send' : null;
|
|
239
239
|
};
|
|
240
240
|
|
|
241
|
+
/**
|
|
242
|
+
* The keys of a stored message's metadata that only the host writes and that
|
|
243
|
+
* some reader takes on its own as what the message is: `system` makes it a
|
|
244
|
+
* host notice, `draftOwner` and `draftIds` one that names those drafts,
|
|
245
|
+
* `tuneOut` a message a tune-out holds from the residents,
|
|
246
|
+
* `coalescingSubject` (beside the occurrence's `eventId`) the delivery of a
|
|
247
|
+
* coalesced occurrence, `deferredWriteId` the landed copy of a deferred
|
|
248
|
+
* write, `external` a module's own row for an outside message,
|
|
249
|
+
* `silentHeartbeat` a row of a silent heartbeat tick, which marks the tick
|
|
250
|
+
* started whoever's row it is (indexSilentTickRows), and
|
|
251
|
+
* `openaiResponsesItems` membrane's mark of an imported Responses history. A
|
|
252
|
+
* server's metadata, a channel message's `metadata` or a push event's
|
|
253
|
+
* `origin`, is stored without them, and the host writes its own after it:
|
|
254
|
+
* MCPL SPEC §16.6 makes metadata the producer's untrusted claims, which a
|
|
255
|
+
* host may disbelieve. Keys a server writes by protocol, such as `tags` and
|
|
256
|
+
* `mentioned`, stay. So does the Discord module's `discordTextFirst`, which
|
|
257
|
+
* it reads only on a row it found by `external`.
|
|
258
|
+
*/
|
|
259
|
+
const HOST_METADATA_KEYS = [
|
|
260
|
+
'system', 'draftOwner', 'draftIds', 'tuneOut', 'coalescingSubject', 'deferredWriteId', 'external',
|
|
261
|
+
'silentHeartbeat', 'openaiResponsesItems',
|
|
262
|
+
] as const;
|
|
263
|
+
|
|
241
264
|
/**
|
|
242
265
|
* True when an injected message is real conversational input — something the
|
|
243
266
|
* agent might actually be replying to — rather than ambient machinery.
|
|
@@ -10345,9 +10368,9 @@ export class AgentFramework {
|
|
|
10345
10368
|
// turn ends. The window stores them here, right after the
|
|
10346
10369
|
// tool_result message, which is exactly where the membrane appends
|
|
10347
10370
|
// them in the live conversation — same participant, same content
|
|
10348
|
-
// blocks,
|
|
10349
|
-
// verbatim, so the next turn's compiled prefix
|
|
10350
|
-
// live request (prompt-cache safe).
|
|
10371
|
+
// blocks, and neither carries the row's metadata — and compile()
|
|
10372
|
+
// emits recent messages verbatim, so the next turn's compiled prefix
|
|
10373
|
+
// byte-matches the live request (prompt-cache safe).
|
|
10351
10374
|
//
|
|
10352
10375
|
// The queue is drained ONLY at the target agent's own boundary:
|
|
10353
10376
|
// draining at another agent's boundary would store the messages
|
|
@@ -10589,9 +10612,13 @@ export class AgentFramework {
|
|
|
10589
10612
|
// Mid-turn messages collected above ride along as injected user
|
|
10590
10613
|
// messages (membrane ≥0.5.72) — appended after the tool_result
|
|
10591
10614
|
// envelope so the next round of THIS turn hears them.
|
|
10615
|
+
// Each goes as its participant and content alone: a stored row's
|
|
10616
|
+
// metadata is the host's record, not input for membrane.
|
|
10592
10617
|
currentState.stream.provideToolResults(
|
|
10593
10618
|
agent.toolResultGuard.submissionResults(membraneResults),
|
|
10594
|
-
midTurnInjections.length > 0
|
|
10619
|
+
midTurnInjections.length > 0
|
|
10620
|
+
? { injectedMessages: midTurnInjections.map(({ participant, content }) => ({ participant, content })) }
|
|
10621
|
+
: undefined,
|
|
10595
10622
|
);
|
|
10596
10623
|
// RFC-006 §3.3: consumption is determined at assembly, and this
|
|
10597
10624
|
// is assembly too when the stream carries the injected messages
|
|
@@ -10789,6 +10816,31 @@ export class AgentFramework {
|
|
|
10789
10816
|
// Built-in MCPL event → context message conversion
|
|
10790
10817
|
// ==========================================================================
|
|
10791
10818
|
|
|
10819
|
+
/** The servers and keys whose drop withoutHostKeys has logged, each as
|
|
10820
|
+
* `${serverId}\u0000${key}`. */
|
|
10821
|
+
private readonly hostKeysDropped = new Set<string>();
|
|
10822
|
+
|
|
10823
|
+
/**
|
|
10824
|
+
* A server's metadata as it is stored: a copy without HOST_METADATA_KEYS.
|
|
10825
|
+
* A server may use one of those names for something of its own, so the
|
|
10826
|
+
* first drop of each key from each server is logged.
|
|
10827
|
+
*/
|
|
10828
|
+
private withoutHostKeys(serverId: string, given: Record<string, unknown> | undefined): Record<string, unknown> {
|
|
10829
|
+
const kept: Record<string, unknown> = { ...given };
|
|
10830
|
+
for (const key of HOST_METADATA_KEYS) {
|
|
10831
|
+
if (!Object.hasOwn(kept, key)) continue;
|
|
10832
|
+
delete kept[key];
|
|
10833
|
+
const logged = `${serverId}\u0000${key}`;
|
|
10834
|
+
if (this.hostKeysDropped.has(logged)) continue;
|
|
10835
|
+
this.hostKeysDropped.add(logged);
|
|
10836
|
+
console.error(
|
|
10837
|
+
`[mcpl] ${serverId}: dropped "${key}" from a message, since only the host writes that key ` +
|
|
10838
|
+
`(logged once for each server and key)`,
|
|
10839
|
+
);
|
|
10840
|
+
}
|
|
10841
|
+
return kept;
|
|
10842
|
+
}
|
|
10843
|
+
|
|
10792
10844
|
/**
|
|
10793
10845
|
* Convert an incoming MCPL channel message to a context message.
|
|
10794
10846
|
* This replaces the old MCPLModule.onProcess() message conversion.
|
|
@@ -10825,7 +10877,7 @@ export class AgentFramework {
|
|
|
10825
10877
|
return undefined;
|
|
10826
10878
|
}
|
|
10827
10879
|
const metadata: Record<string, unknown> = {
|
|
10828
|
-
...event.metadata,
|
|
10880
|
+
...this.withoutHostKeys(event.serverId, event.metadata),
|
|
10829
10881
|
...(event.eventId ? { eventId: event.eventId } : {}),
|
|
10830
10882
|
...(event.coalescingSubject ? { coalescingSubject: event.coalescingSubject } : {}),
|
|
10831
10883
|
channelId: event.channelId,
|
|
@@ -12267,7 +12319,7 @@ export class AgentFramework {
|
|
|
12267
12319
|
}
|
|
12268
12320
|
|
|
12269
12321
|
const metadata: Record<string, unknown> = {
|
|
12270
|
-
...event.origin,
|
|
12322
|
+
...this.withoutHostKeys(event.serverId, event.origin),
|
|
12271
12323
|
serverId: event.serverId,
|
|
12272
12324
|
featureSet: event.featureSet,
|
|
12273
12325
|
eventId: event.eventId,
|
|
@@ -26,9 +26,13 @@ import type { ContentBlock } from '@animalabs/membrane';
|
|
|
26
26
|
* the hidden thinking is the bulk of the output, and this stamp is what makes
|
|
27
27
|
* the compiled budget honest (see context-manager `SIGNATURE_CHARS_PER_TOKEN`).
|
|
28
28
|
*
|
|
29
|
-
* Wire safety:
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* Wire safety: the stamp is context-manager's pricing metadata, and no field
|
|
30
|
+
* a provider takes. The Messages API refuses a block that carries it
|
|
31
|
+
* ("Extra inputs are not permitted"), and membrane (0.5.87 and 0.6.0) sends a
|
|
32
|
+
* `redacted_thinking` block to Anthropic and Bedrock with every field it has.
|
|
33
|
+
* So an agent's request builders drop the stamp from the compiled messages
|
|
34
|
+
* (`withoutTokenEstimates`), and the stored block, which context-manager
|
|
35
|
+
* prices, keeps it. The signature itself is copied, never edited.
|
|
32
36
|
*/
|
|
33
37
|
const PROSE_CHARS_PER_TOKEN = 2.9;
|
|
34
38
|
const DENSE_CHARS_PER_TOKEN = 2.3;
|
|
@@ -93,3 +97,30 @@ export function stampThinkingTokenEstimates(blocks: ContentBlock[], outputTokens
|
|
|
93
97
|
return { ...block, tokenEstimate } as ContentBlock;
|
|
94
98
|
});
|
|
95
99
|
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A request's messages with no top-level block carrying `tokenEstimate`. Both
|
|
103
|
+
* of an agent's request builders call it on the compiled messages: a
|
|
104
|
+
* framework turn's (`Agent.assembleActivationRequest`) and the direct API's
|
|
105
|
+
* (`Agent.runInferenceWithInjections`). A block that carries the stamp is
|
|
106
|
+
* copied without it, and so is the message that holds it. Every other
|
|
107
|
+
* message and block is passed on as it is, and nothing is changed in place,
|
|
108
|
+
* so the stored block keeps its stamp.
|
|
109
|
+
*/
|
|
110
|
+
export function withoutTokenEstimates<T extends { content: ContentBlock[] }>(messages: T[]): T[] {
|
|
111
|
+
return messages.map((message) => {
|
|
112
|
+
if (!message.content.some(isStamped)) return message;
|
|
113
|
+
return {
|
|
114
|
+
...message,
|
|
115
|
+
content: message.content.map((block) => {
|
|
116
|
+
if (!isStamped(block)) return block;
|
|
117
|
+
const { tokenEstimate: _stamp, ...unstamped } = block as ContentBlock & { tokenEstimate?: unknown };
|
|
118
|
+
return unstamped as ContentBlock;
|
|
119
|
+
}),
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isStamped(block: ContentBlock): boolean {
|
|
125
|
+
return typeof block === 'object' && block !== null && 'tokenEstimate' in block;
|
|
126
|
+
}
|