@animalabs/agent-framework 0.17.0 → 0.19.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/dist/src/framework.d.ts +8 -5
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +26 -14
- package/dist/src/framework.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +15 -0
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +50 -4
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/thinking-token-stamp.d.ts +7 -0
- package/dist/src/thinking-token-stamp.d.ts.map +1 -0
- package/dist/src/thinking-token-stamp.js +103 -0
- package/dist/src/thinking-token-stamp.js.map +1 -0
- package/dist/test/channels-changed-respond-first.test.d.ts +2 -0
- package/dist/test/channels-changed-respond-first.test.d.ts.map +1 -0
- package/dist/test/channels-changed-respond-first.test.js +74 -0
- package/dist/test/channels-changed-respond-first.test.js.map +1 -0
- package/dist/test/journal-tool.test.d.ts +2 -0
- package/dist/test/journal-tool.test.d.ts.map +1 -0
- package/dist/test/journal-tool.test.js +33 -0
- package/dist/test/journal-tool.test.js.map +1 -0
- package/dist/test/overbudget-drain-breaker.test.js +44 -4
- package/dist/test/overbudget-drain-breaker.test.js.map +1 -1
- package/dist/test/thinking-token-stamp.test.d.ts +12 -0
- package/dist/test/thinking-token-stamp.test.d.ts.map +1 -0
- package/dist/test/thinking-token-stamp.test.js +129 -0
- package/dist/test/thinking-token-stamp.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/framework.ts +29 -14
- package/src/mcpl/channel-registry.ts +55 -3
- package/src/thinking-token-stamp.ts +95 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { CapabilityGrant, ALL_CAPABILITY_PATHS } from '../src/mcpl/capability-grant.js';
|
|
2
|
+
import { test } from 'node:test';
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { ChannelRegistry } from '../src/mcpl/channel-registry.js';
|
|
5
|
+
/**
|
|
6
|
+
* #160: a server that serves one request at a time and announces a channel
|
|
7
|
+
* from inside a request (a `refresh_channels` tool call) cannot read the
|
|
8
|
+
* channels/open or channels/close that reconciling sends until its
|
|
9
|
+
* `channels/changed` is answered. The mock models exactly that: every
|
|
10
|
+
* lifecycle request the host sends stays unread until the responder fires.
|
|
11
|
+
*/
|
|
12
|
+
function makeSerializedServer() {
|
|
13
|
+
const order = [];
|
|
14
|
+
let release;
|
|
15
|
+
const loopFree = new Promise((resolve) => { release = resolve; });
|
|
16
|
+
const serve = (kind) => async (params) => {
|
|
17
|
+
order.push(`${kind}-sent:${params.channelId}`);
|
|
18
|
+
await loopFree;
|
|
19
|
+
order.push(`${kind}-served:${params.channelId}`);
|
|
20
|
+
return {};
|
|
21
|
+
};
|
|
22
|
+
const mockServer = {
|
|
23
|
+
grant: new CapabilityGrant(new Set(ALL_CAPABILITY_PATHS), []),
|
|
24
|
+
sendChannelsOpen: serve('open'),
|
|
25
|
+
sendChannelsClose: serve('close'),
|
|
26
|
+
};
|
|
27
|
+
const registry = new ChannelRegistry({ getServer: (_id) => mockServer }, {}, () => { }, () => { });
|
|
28
|
+
let response;
|
|
29
|
+
const responder = {
|
|
30
|
+
respond: (result) => {
|
|
31
|
+
order.push('responded');
|
|
32
|
+
response = result;
|
|
33
|
+
release();
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
return { registry, responder, order, response: () => response };
|
|
37
|
+
}
|
|
38
|
+
function withinMs(promise, ms, what) {
|
|
39
|
+
let timer;
|
|
40
|
+
const timeout = new Promise((_, reject) => {
|
|
41
|
+
timer = setTimeout(() => reject(new Error(`${what} did not settle within ${ms}ms`)), ms);
|
|
42
|
+
});
|
|
43
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
44
|
+
}
|
|
45
|
+
test('channels/changed answers before reconciling, so a serialized server is not deadlocked (#160)', async () => {
|
|
46
|
+
const { registry, responder, order, response } = makeSerializedServer();
|
|
47
|
+
registry.setSubscriptionPolicy('zulip', ['zulip:devops']);
|
|
48
|
+
await withinMs(registry.handleChanged('zulip', {
|
|
49
|
+
added: [
|
|
50
|
+
{ id: 'zulip:devops', type: 'zulip', label: '#devops', direction: 'bidirectional' },
|
|
51
|
+
{ id: 'zulip:random', type: 'zulip', label: '#random', direction: 'bidirectional' },
|
|
52
|
+
],
|
|
53
|
+
}, responder), 2000, 'handleChanged');
|
|
54
|
+
assert.deepEqual(response(), {
|
|
55
|
+
results: [
|
|
56
|
+
{ id: 'zulip:devops', accepted: true },
|
|
57
|
+
{ id: 'zulip:random', accepted: true },
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
assert.equal(order[0], 'responded', `the response goes out before any lifecycle request: ${order.join(', ')}`);
|
|
61
|
+
// Reconciliation still runs to completion after the response: the policy
|
|
62
|
+
// channel is opened, the other is closed.
|
|
63
|
+
assert.ok(order.includes('open-served:zulip:devops'), order.join(', '));
|
|
64
|
+
assert.ok(order.includes('close-served:zulip:random'), order.join(', '));
|
|
65
|
+
});
|
|
66
|
+
test('channels/changed with nothing added still answers once, without reconciling', async () => {
|
|
67
|
+
const { registry, responder, order, response } = makeSerializedServer();
|
|
68
|
+
await withinMs(registry.handleChanged('zulip', { removed: ['zulip:gone'] }, responder), 2000, 'handleChanged');
|
|
69
|
+
assert.deepEqual(order, ['responded']);
|
|
70
|
+
assert.deepEqual(response(), {
|
|
71
|
+
results: [{ id: 'zulip:gone', accepted: false, reason: 'not registered' }],
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
//# sourceMappingURL=channels-changed-respond-first.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channels-changed-respond-first.test.js","sourceRoot":"","sources":["../../test/channels-changed-respond-first.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal-tool.test.d.ts","sourceRoot":"","sources":["../../test/journal-tool.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { ChannelRegistry } from '../src/mcpl/channel-registry.js';
|
|
4
|
+
// `journal` — private long-form notes (sill, 2026-09-19). Long prose kept in
|
|
5
|
+
// `skip_reply.reason` / `think.content` gets every memory-compression request
|
|
6
|
+
// over that history refused; a note-taking tool does not. context-manager's
|
|
7
|
+
// tool-prose hoist rung rewrites old history into calls to this tool and
|
|
8
|
+
// mirrors its result wording (DEFAULT_TOOL_PROSE_RESULT) — the literal below
|
|
9
|
+
// is that contract.
|
|
10
|
+
function makeRegistry() {
|
|
11
|
+
return new ChannelRegistry({}, {}, () => { }, () => { }, {});
|
|
12
|
+
}
|
|
13
|
+
test('journal is a declared tool with a required content argument', () => {
|
|
14
|
+
const tool = makeRegistry().getChannelTools().find((t) => t.name === 'journal');
|
|
15
|
+
assert.ok(tool, 'journal is exposed');
|
|
16
|
+
assert.deepEqual(tool.inputSchema.required, ['content']);
|
|
17
|
+
assert.match(tool.description, /NOT sent/);
|
|
18
|
+
});
|
|
19
|
+
test('journal records privately: no echo, does not end the turn, wording matches the context-manager mirror', async () => {
|
|
20
|
+
const entry = 'a long private reflection '.repeat(40);
|
|
21
|
+
const result = await makeRegistry().handleChannelToolCall('journal', { content: entry });
|
|
22
|
+
assert.equal(result.success, true);
|
|
23
|
+
assert.equal(result.endTurn, undefined, 'journal must not end the turn');
|
|
24
|
+
assert.equal(JSON.stringify(result.data), '{"recorded":true,"note":"Journal entry recorded (private — not sent anywhere)."}');
|
|
25
|
+
assert.ok(!JSON.stringify(result).includes(entry.slice(0, 40)), 'entry is not echoed back');
|
|
26
|
+
});
|
|
27
|
+
test('skip_reply.reason asks for one short line and points at journal', () => {
|
|
28
|
+
const tool = makeRegistry().getChannelTools().find((t) => t.name === 'skip_reply');
|
|
29
|
+
const reason = tool.inputSchema.properties.reason.description ?? '';
|
|
30
|
+
assert.match(reason, /short line/);
|
|
31
|
+
assert.match(reason, /journal\(\)/);
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=journal-tool.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal-tool.test.js","sourceRoot":"","sources":["../../test/journal-tool.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAIlE,6EAA6E;AAC7E,8EAA8E;AAC9E,4EAA4E;AAC5E,yEAAyE;AACzE,6EAA6E;AAC7E,oBAAoB;AAEpB,SAAS,YAAY;IACnB,OAAO,IAAI,eAAe,CAAC,EAAwB,EAAE,EAAuB,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AACxG,CAAC;AAED,IAAI,CAAC,6DAA6D,EAAE,GAAG,EAAE;IACvE,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC,eAAe,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;IAChF,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC;IACtC,MAAM,CAAC,SAAS,CAAE,IAAK,CAAC,WAAsC,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IACtF,MAAM,CAAC,KAAK,CAAC,IAAK,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;AAC9C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uGAAuG,EAAE,KAAK,IAAI,EAAE;IACvH,MAAM,KAAK,GAAG,4BAA4B,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM,YAAY,EAAE,CAAC,qBAAqB,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IACzF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACnC,MAAM,CAAC,KAAK,CAAE,MAAgC,CAAC,OAAO,EAAE,SAAS,EAAE,+BAA+B,CAAC,CAAC;IACpG,MAAM,CAAC,KAAK,CACV,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,EAC3B,kFAAkF,CACnF,CAAC;IACF,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,0BAA0B,CAAC,CAAC;AAC9F,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iEAAiE,EAAE,GAAG,EAAE;IAC3E,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC,eAAe,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,CAAE,CAAC;IACpF,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,UAAW,CAAC,MAAO,CAAC,WAAW,IAAI,EAAE,CAAC;IACtE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACnC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AACtC,CAAC,CAAC,CAAC"}
|
|
@@ -6,15 +6,18 @@
|
|
|
6
6
|
* hard-down, zero self-rescue). noteInferenceExhausted breaks it by kicking
|
|
7
7
|
* the strategy drain (cm.tick()) directly.
|
|
8
8
|
*
|
|
9
|
-
* The trigger is the classified errorType 'over_budget'
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* The trigger is the classified errorType 'over_budget'. Since
|
|
10
|
+
* context-manager#41/#71 the classes are exported from CM's root, so the
|
|
11
|
+
* primary match is a real cross-package `instanceof`; `err.name` stays as a
|
|
12
|
+
* fallback for dual-CM-copy deployments, and the message-prose match remains
|
|
13
|
+
* a last resort for serialized reasons. These tests pin all of it so a CM
|
|
14
|
+
* message rewording — or a second CM copy — cannot silently kill the
|
|
13
15
|
* breaker.
|
|
14
16
|
*/
|
|
15
17
|
import { test } from 'node:test';
|
|
16
18
|
import assert from 'node:assert/strict';
|
|
17
19
|
import { AgentFramework } from '../src/framework.js';
|
|
20
|
+
import { OverBudgetError, UncoveredDropError } from '@animalabs/context-manager';
|
|
18
21
|
import { MembraneError } from '@animalabs/membrane';
|
|
19
22
|
function makeHarness(tick) {
|
|
20
23
|
const cm = {
|
|
@@ -48,6 +51,43 @@ async function settle(rounds = 3) {
|
|
|
48
51
|
await new Promise((r) => setImmediate(r));
|
|
49
52
|
}
|
|
50
53
|
const OVER_BUDGET_REASON = 'Compile plan would exceed hard budget: head=41200 tail=8100 middle=62000 budget=100000';
|
|
54
|
+
test('classification: a REAL context-manager OverBudgetError classifies as over_budget', () => {
|
|
55
|
+
const { fw, restore } = makeHarness(async () => { });
|
|
56
|
+
restore();
|
|
57
|
+
const real = new OverBudgetError({
|
|
58
|
+
budget: 4000,
|
|
59
|
+
actual: 8000,
|
|
60
|
+
diagnostics: { headTokens: 1, tailTokens: 2, middleTokens: 3, middleChunkCount: 1, deepestLevel: 1 },
|
|
61
|
+
});
|
|
62
|
+
assert.deepEqual(fw.classifyInferenceError(real), { errorType: 'over_budget' });
|
|
63
|
+
});
|
|
64
|
+
test('classification: a REAL UncoveredDropError classifies as context_refusal', () => {
|
|
65
|
+
const { fw, restore } = makeHarness(async () => { });
|
|
66
|
+
restore();
|
|
67
|
+
const real = new UncoveredDropError({
|
|
68
|
+
droppedIds: ['m1'],
|
|
69
|
+
site: 'selectHierarchical',
|
|
70
|
+
diagnostics: { budget: 4000, totalTokens: 8000 },
|
|
71
|
+
});
|
|
72
|
+
assert.deepEqual(fw.classifyInferenceError(real), { errorType: 'context_refusal' });
|
|
73
|
+
});
|
|
74
|
+
// A real instance still carries the constructor's `name`, so the two cases
|
|
75
|
+
// above classify through the name fallback too. Overwriting `name` leaves
|
|
76
|
+
// instanceof as the only path that can decide, which pins it.
|
|
77
|
+
test('classification: a real OverBudgetError classifies by instanceof even when its name is overwritten', () => {
|
|
78
|
+
const { fw, restore } = makeHarness(async () => { });
|
|
79
|
+
restore();
|
|
80
|
+
const real = new OverBudgetError({ budget: 1, actual: 2, diagnostics: { headTokens: 1, tailTokens: 1, middleTokens: 1, middleChunkCount: 1, deepestLevel: 1 } });
|
|
81
|
+
Object.defineProperty(real, 'name', { value: 'Wrapped' });
|
|
82
|
+
assert.deepEqual(fw.classifyInferenceError(real), { errorType: 'over_budget' });
|
|
83
|
+
});
|
|
84
|
+
test('classification: a real UncoveredDropError classifies by instanceof even when its name is overwritten', () => {
|
|
85
|
+
const { fw, restore } = makeHarness(async () => { });
|
|
86
|
+
restore();
|
|
87
|
+
const real = new UncoveredDropError({ droppedIds: ['m1'], site: 's', diagnostics: { budget: 1, totalTokens: 2 } });
|
|
88
|
+
Object.defineProperty(real, 'name', { value: 'Wrapped' });
|
|
89
|
+
assert.deepEqual(fw.classifyInferenceError(real), { errorType: 'context_refusal' });
|
|
90
|
+
});
|
|
51
91
|
test('classification: err.name OverBudgetError → over_budget, regardless of message wording', () => {
|
|
52
92
|
const fw = Object.create(AgentFramework.prototype);
|
|
53
93
|
// The load-bearing pin: classification must NOT depend on CM's message
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overbudget-drain-breaker.test.js","sourceRoot":"","sources":["../../test/overbudget-drain-breaker.test.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"overbudget-drain-breaker.test.js","sourceRoot":"","sources":["../../test/overbudget-drain-breaker.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signed thinking blocks are stamped with their replay price at persist time.
|
|
3
|
+
*
|
|
4
|
+
* On keep-all models the hidden chain of thought behind a signed thinking
|
|
5
|
+
* block is replayed and billed as input on every later call; the only exact
|
|
6
|
+
* measure of its size is this call's `usage.output_tokens` minus the visible
|
|
7
|
+
* blocks. The framework stamps that residual as `tokenEstimate` on the
|
|
8
|
+
* signed blocks (context-manager prefers the stamp over any heuristic), per
|
|
9
|
+
* round for tool-use turns and on the trailing content at completion.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=thinking-token-stamp.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thinking-token-stamp.test.d.ts","sourceRoot":"","sources":["../../test/thinking-token-stamp.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signed thinking blocks are stamped with their replay price at persist time.
|
|
3
|
+
*
|
|
4
|
+
* On keep-all models the hidden chain of thought behind a signed thinking
|
|
5
|
+
* block is replayed and billed as input on every later call; the only exact
|
|
6
|
+
* measure of its size is this call's `usage.output_tokens` minus the visible
|
|
7
|
+
* blocks. The framework stamps that residual as `tokenEstimate` on the
|
|
8
|
+
* signed blocks (context-manager prefers the stamp over any heuristic), per
|
|
9
|
+
* round for tool-use turns and on the trailing content at completion.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, it } from 'node:test';
|
|
12
|
+
import assert from 'node:assert/strict';
|
|
13
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { AgentFramework } from '../src/index.js';
|
|
17
|
+
import { stampThinkingTokenEstimates } from '../src/thinking-token-stamp.js';
|
|
18
|
+
import { MockMembrane, createMockResponse } from './helpers/mock-membrane.js';
|
|
19
|
+
const SIG_A = 'A'.repeat(4_000);
|
|
20
|
+
const SIG_B = 'B'.repeat(3_000);
|
|
21
|
+
describe('stampThinkingTokenEstimates (pure)', () => {
|
|
22
|
+
it('stamps the residual of output tokens over visible blocks on the signed block', () => {
|
|
23
|
+
const blocks = [
|
|
24
|
+
{ type: 'thinking', thinking: '', signature: SIG_A },
|
|
25
|
+
{ type: 'text', text: 'hello' },
|
|
26
|
+
];
|
|
27
|
+
const out = stampThinkingTokenEstimates(blocks, 3_000);
|
|
28
|
+
assert.equal(out[0].tokenEstimate, 3_000 - Math.ceil(5 / 2.9));
|
|
29
|
+
assert.equal(out[0].signature, SIG_A);
|
|
30
|
+
assert.equal(out[1], blocks[1], 'non-carrier blocks are the same object');
|
|
31
|
+
assert.equal(blocks[0].tokenEstimate, undefined, 'input block is not mutated');
|
|
32
|
+
});
|
|
33
|
+
it('splits the residual across several carriers by signature length', () => {
|
|
34
|
+
const blocks = [
|
|
35
|
+
{ type: 'thinking', thinking: '', signature: 'A'.repeat(3_000) },
|
|
36
|
+
{ type: 'tool_use', id: 't1', name: 'x', input: {} },
|
|
37
|
+
{ type: 'redacted_thinking', data: 'B'.repeat(1_000) },
|
|
38
|
+
];
|
|
39
|
+
const out = stampThinkingTokenEstimates(blocks, 4_021); // tool_use {} = 1 + 20 tokens
|
|
40
|
+
assert.equal(out[0].tokenEstimate, 3_000);
|
|
41
|
+
assert.equal(out[2].tokenEstimate, 1_000);
|
|
42
|
+
});
|
|
43
|
+
it('leaves unsigned thinking, already-stamped blocks and non-positive residuals alone', () => {
|
|
44
|
+
const unsigned = [{ type: 'thinking', thinking: 'visible reasoning' }];
|
|
45
|
+
assert.equal(stampThinkingTokenEstimates(unsigned, 500), unsigned);
|
|
46
|
+
const stamped = [{ type: 'thinking', thinking: '', signature: SIG_A, tokenEstimate: 42 }];
|
|
47
|
+
assert.equal(stampThinkingTokenEstimates(stamped, 5_000)[0].tokenEstimate, 42);
|
|
48
|
+
const overshoot = [
|
|
49
|
+
{ type: 'thinking', thinking: '', signature: SIG_A },
|
|
50
|
+
{ type: 'text', text: 'x'.repeat(10_000) },
|
|
51
|
+
];
|
|
52
|
+
assert.equal(stampThinkingTokenEstimates(overshoot, 100), overshoot);
|
|
53
|
+
assert.equal(stampThinkingTokenEstimates(overshoot, 0), overshoot);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
class EchoModule {
|
|
57
|
+
name = 'test';
|
|
58
|
+
async start(_ctx) { }
|
|
59
|
+
async stop() { }
|
|
60
|
+
getTools() {
|
|
61
|
+
return [{
|
|
62
|
+
name: 'echo',
|
|
63
|
+
description: 'Echoes the input',
|
|
64
|
+
inputSchema: { type: 'object', properties: { message: { type: 'string' } }, required: ['message'] },
|
|
65
|
+
}];
|
|
66
|
+
}
|
|
67
|
+
async handleToolCall(call) {
|
|
68
|
+
return { success: true, data: { echoed: call.input.message } };
|
|
69
|
+
}
|
|
70
|
+
async onProcess(event, _state) {
|
|
71
|
+
if (event.type !== 'external-message')
|
|
72
|
+
return {};
|
|
73
|
+
return {
|
|
74
|
+
addMessages: [{ participant: 'User', content: [{ type: 'text', text: String(event.content) }] }],
|
|
75
|
+
requestInference: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
describe('thinking token stamp through the stream loop', () => {
|
|
80
|
+
it('stamps each round from that call\'s own output tokens (membrane usage is cumulative)', async () => {
|
|
81
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'thinking-stamp-'));
|
|
82
|
+
const membrane = new MockMembrane();
|
|
83
|
+
const round1 = createMockResponse([
|
|
84
|
+
{ type: 'thinking', thinking: '', signature: SIG_A },
|
|
85
|
+
{ type: 'text', text: 'looking' },
|
|
86
|
+
{ type: 'tool_use', id: 'call_1', name: 'test--echo', input: { message: 'one' } },
|
|
87
|
+
], 'tool_use');
|
|
88
|
+
round1.usage = { inputTokens: 100, outputTokens: 2_000 };
|
|
89
|
+
const round2 = createMockResponse([
|
|
90
|
+
{ type: 'thinking', thinking: '', signature: SIG_B },
|
|
91
|
+
{ type: 'text', text: 'done' },
|
|
92
|
+
]);
|
|
93
|
+
// Membrane reports usage CUMULATIVELY across the tool loop.
|
|
94
|
+
round2.usage = { inputTokens: 250, outputTokens: 3_500 };
|
|
95
|
+
membrane.pushResponse(round1);
|
|
96
|
+
membrane.pushResponse(round2);
|
|
97
|
+
const framework = await AgentFramework.create({
|
|
98
|
+
storePath: join(tempDir, 'test.chronicle'),
|
|
99
|
+
membrane: membrane.asMembrane(),
|
|
100
|
+
agents: [{ name: 'assistant', model: 'test-model', systemPrompt: 'Test' }],
|
|
101
|
+
modules: [new EchoModule()],
|
|
102
|
+
});
|
|
103
|
+
try {
|
|
104
|
+
framework.pushEvent({ type: 'external-message', source: 'test', content: 'go', metadata: {} });
|
|
105
|
+
await framework.runUntilIdle();
|
|
106
|
+
const cm = framework.getAgent('assistant').getContextManager();
|
|
107
|
+
const msgs = (cm.queryMessages({}).messages ?? []);
|
|
108
|
+
const thinking = msgs.flatMap((m) => m.content).filter((b) => b.type === 'thinking');
|
|
109
|
+
assert.equal(thinking.length, 2, 'both rounds persisted their thinking block');
|
|
110
|
+
const [t1, t2] = thinking;
|
|
111
|
+
assert.equal(t1.signature, SIG_A);
|
|
112
|
+
assert.equal(t2.signature, SIG_B);
|
|
113
|
+
// Round 1: 2000 output tokens minus 'looking' and the tool_use JSON.
|
|
114
|
+
const toolJson = JSON.stringify({ message: 'one' }).length;
|
|
115
|
+
const visible1 = Math.ceil('looking'.length / 2.9) + Math.ceil(toolJson / 2.3) + 20;
|
|
116
|
+
assert.equal(t1.tokenEstimate, 2_000 - visible1);
|
|
117
|
+
// Round 2: (3500 - 2000) output tokens minus 'done' — not the cumulative 3500.
|
|
118
|
+
assert.equal(t2.tokenEstimate, 1_500 - Math.ceil('done'.length / 2.9));
|
|
119
|
+
const others = msgs.flatMap((m) => m.content).filter((b) => b.type !== 'thinking');
|
|
120
|
+
for (const b of others)
|
|
121
|
+
assert.equal(b.tokenEstimate, undefined, `${b.type} must not be stamped`);
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
await framework.stop();
|
|
125
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=thinking-token-stamp.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thinking-token-stamp.test.js","sourceRoot":"","sources":["../../test/thinking-token-stamp.test.ts"],"names":[],"mappings":"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"}
|