@sayknow-cli/agent-core 0.2.4 → 0.2.6
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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.6.2] - 2026-06-19
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Token accounting no longer depends on a native embedded tokenizer. Token usage now anchors on provider-reported usage (`calculatePromptTokens`) and estimates only the unsent delta with a cheap heuristic (~chars/4 × 1.2); emergency compaction floors are unchanged. Compaction, branch summarization, and fork-seed paths were repointed off the removed native token-estimate alias. Part of dropping the bundled tiktoken/o200k tokenizer (#879).
|
|
10
|
+
|
|
5
11
|
## [0.5.4] - 2026-06-17
|
|
6
12
|
|
|
7
13
|
### Fixed
|
|
@@ -99,33 +99,16 @@ export declare const DEFAULT_EMERGENCY_COMPACTION_LIMITS: EmergencyCompactionLim
|
|
|
99
99
|
export declare function emergencyCompactionReason(sample: EmergencyCompactionSample, limits?: EmergencyCompactionLimits): CompactionTriggerReason | null;
|
|
100
100
|
export declare function resolveThresholdTokens(contextWindow: number, settings: CompactionSettings, maxOutputTokens?: number): number;
|
|
101
101
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* only for context-changing decisions (compaction trigger/cut points, pruning
|
|
108
|
-
* budgets, branch summarization, fork-context seeding, context-limit
|
|
109
|
-
* enforcement). For display-only totals use
|
|
110
|
-
* {@link estimateMessageTokensHeuristic}.
|
|
111
|
-
*/
|
|
112
|
-
export declare function countMessageTokensNativeO200k(message: AgentMessage): number;
|
|
113
|
-
/**
|
|
114
|
-
* Backwards-compatible alias for {@link countMessageTokensNativeO200k}.
|
|
115
|
-
* Existing callers treat this as the canonical message-token estimator for
|
|
116
|
-
* context-changing decisions.
|
|
117
|
-
*/
|
|
118
|
-
export declare const estimateTokens: typeof countMessageTokensNativeO200k;
|
|
119
|
-
/**
|
|
120
|
-
* Cheap, native-free token estimate for a message. Suitable ONLY for
|
|
121
|
-
* display/init surfaces (status line, /context report, HUD totals) — never
|
|
122
|
-
* for context-changing decisions, which must use
|
|
123
|
-
* {@link countMessageTokensNativeO200k}.
|
|
102
|
+
* Native-free chars/4 token estimate for a message. This is the only message
|
|
103
|
+
* token estimator: provider usage (see {@link calculatePromptTokens}) anchors
|
|
104
|
+
* the already-sent context, and this covers unsent/trailing deltas, per-entry
|
|
105
|
+
* budgeting, and display surfaces. Callers add a conservative inflation factor
|
|
106
|
+
* where compaction-threshold safety requires it.
|
|
124
107
|
*/
|
|
125
108
|
export declare function estimateMessageTokensHeuristic(message: AgentMessage): number;
|
|
126
109
|
/**
|
|
127
|
-
*
|
|
128
|
-
* counterpart of
|
|
110
|
+
* Native-free chars/4 token estimate for plain string fragments. Fragment-level
|
|
111
|
+
* counterpart of {@link estimateMessageTokensHeuristic}.
|
|
129
112
|
*/
|
|
130
113
|
export declare function estimateTextTokensHeuristic(fragments: string | readonly string[]): number;
|
|
131
114
|
export declare function estimateEntryTokens(entry: SessionEntry): number;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@sayknow-cli/agent-core",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.6",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://github.com/jaybeyond/Sayknow_CLI",
|
|
7
7
|
"author": "jaybeyond",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"fmt": "biome format --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@sayknow-cli/ai": "0.2.
|
|
39
|
-
"@sayknow-cli/natives": "0.2.
|
|
40
|
-
"@sayknow-cli/utils": "0.2.
|
|
38
|
+
"@sayknow-cli/ai": "0.2.6",
|
|
39
|
+
"@sayknow-cli/natives": "0.2.6",
|
|
40
|
+
"@sayknow-cli/utils": "0.2.6",
|
|
41
41
|
"@opentelemetry/api": "^1.9.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
@@ -9,7 +9,7 @@ import type { Model, ProviderSessionState } from "@sayknow-cli/ai";
|
|
|
9
9
|
import { prompt } from "@sayknow-cli/utils";
|
|
10
10
|
import { type AgentTelemetry, instrumentedCompleteSimple } from "../telemetry";
|
|
11
11
|
import type { AgentMessage } from "../types";
|
|
12
|
-
import {
|
|
12
|
+
import { estimateMessageTokensHeuristic } from "./compaction";
|
|
13
13
|
import type { ReadonlySessionManager, SessionEntry } from "./entries";
|
|
14
14
|
import {
|
|
15
15
|
type ConvertToLlm,
|
|
@@ -243,7 +243,7 @@ export function prepareBranchEntries(entries: SessionEntry[], tokenBudget: numbe
|
|
|
243
243
|
// Extract file ops from assistant messages (tool calls)
|
|
244
244
|
extractFileOpsFromMessage(message, fileOps);
|
|
245
245
|
|
|
246
|
-
const tokens =
|
|
246
|
+
const tokens = estimateMessageTokensHeuristic(message);
|
|
247
247
|
|
|
248
248
|
// Check budget before adding
|
|
249
249
|
if (tokenBudget > 0 && totalTokens + tokens > tokenBudget) {
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* and after compaction the session is reloaded.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { createRequire } from "node:module";
|
|
9
8
|
import {
|
|
10
9
|
type AssistantMessage,
|
|
11
10
|
Effort,
|
|
@@ -15,7 +14,7 @@ import {
|
|
|
15
14
|
type ProviderSessionState,
|
|
16
15
|
type Usage,
|
|
17
16
|
} from "@sayknow-cli/ai";
|
|
18
|
-
import {
|
|
17
|
+
import { logger, prompt } from "@sayknow-cli/utils";
|
|
19
18
|
import { type AgentTelemetry, instrumentedCompleteSimple } from "../telemetry";
|
|
20
19
|
import type { AgentMessage, AgentTool } from "../types";
|
|
21
20
|
import type { CompactionEntry, SessionEntry } from "./entries";
|
|
@@ -317,86 +316,17 @@ export function resolveThresholdTokens(
|
|
|
317
316
|
* matching what providers typically bill for inline images.
|
|
318
317
|
*/
|
|
319
318
|
const IMAGE_TOKEN_ESTIMATE = 1200;
|
|
320
|
-
const SOURCE_NATIVE_TOKENIZER_ENTRYPOINT = "../../../natives/native/index.js";
|
|
321
|
-
const COMPILED_NATIVE_TOKENIZER_ENTRYPOINT = "/$bunfs/root/packages/natives/native/index.js";
|
|
322
|
-
|
|
323
|
-
const requireFromCompaction = createRequire(import.meta.url);
|
|
324
|
-
|
|
325
|
-
interface NativeTokenizerModule {
|
|
326
|
-
countTokens(input: string | string[], encoding?: unknown): number;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
319
|
/**
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
* Do not resolve this via a package-name dynamic require of
|
|
337
|
-
* `@sayknow-cli/natives`: Bun standalone binaries cannot satisfy those from
|
|
338
|
-
* `$bunfs`. The sibling-package source path is stable for workspace and
|
|
339
|
-
* package-install layouts:
|
|
340
|
-
*
|
|
341
|
-
* - workspace: `packages/agent` -> `packages/natives`
|
|
342
|
-
* - npm/bun install: `node_modules/@sayknow-cli/agent-core` ->
|
|
343
|
-
* `node_modules/@sayknow-cli/natives`
|
|
344
|
-
*
|
|
345
|
-
* Bun rewrites `createRequire(import.meta.url)` to the compiled executable
|
|
346
|
-
* root (`/$bunfs/root/skc-*`) in standalone binaries, so compiled mode uses the
|
|
347
|
-
* absolute bunfs module path emitted by the binary build scripts.
|
|
320
|
+
* Estimate tokens for collected message fragments using the native-free
|
|
321
|
+
* heuristic. Provider usage is the authoritative anchor for context-changing
|
|
322
|
+
* decisions (see {@link calculatePromptTokens}); this chars/4 estimate covers
|
|
323
|
+
* only unsent/trailing deltas and per-entry budgeting, and callers add a
|
|
324
|
+
* conservative inflation factor where threshold safety requires it.
|
|
348
325
|
*/
|
|
349
|
-
let cachedNativeCountTokens: ((input: string | string[], encoding?: unknown) => number) | null = null;
|
|
350
|
-
|
|
351
|
-
function nativeTokenizerEntrypoint(): string {
|
|
352
|
-
return isCompiledBinary() ? COMPILED_NATIVE_TOKENIZER_ENTRYPOINT : SOURCE_NATIVE_TOKENIZER_ENTRYPOINT;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/** Max total fragment chars sent to the synchronous native tokenizer (F22). */
|
|
356
|
-
const MAX_NATIVE_TOKENIZE_CHARS = 2 * 1024 * 1024;
|
|
357
|
-
|
|
358
|
-
function nativeCountTokens(fragments: string[]): number {
|
|
359
|
-
let totalChars = 0;
|
|
360
|
-
for (const fragment of fragments) totalChars += fragment.length;
|
|
361
|
-
if (totalChars > MAX_NATIVE_TOKENIZE_CHARS) {
|
|
362
|
-
// F22: skip the synchronous native BPE tokenizer (materializes a ~39MB table and is
|
|
363
|
-
// O(text)) on pathologically large inputs; the cheap chars/token heuristic is more
|
|
364
|
-
// than accurate enough for size/budget decisions and never blocks the event loop.
|
|
365
|
-
return estimateTextTokensHeuristic(fragments);
|
|
366
|
-
}
|
|
367
|
-
if (!cachedNativeCountTokens) {
|
|
368
|
-
const natives = requireFromCompaction(nativeTokenizerEntrypoint()) as NativeTokenizerModule;
|
|
369
|
-
cachedNativeCountTokens = natives.countTokens;
|
|
370
|
-
}
|
|
371
|
-
return cachedNativeCountTokens(fragments);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
326
|
function countCollectedMessageFragments(collected: { fragments: string[]; extra: number }): number {
|
|
375
|
-
return
|
|
327
|
+
return estimateTextTokensHeuristic(collected.fragments) + collected.extra;
|
|
376
328
|
}
|
|
377
329
|
|
|
378
|
-
/**
|
|
379
|
-
* Estimate token count for a message using the native o200k tokenizer.
|
|
380
|
-
* Exact for o200k only; an approximation for Anthropic/other model families
|
|
381
|
-
* (Anthropic doesn't publish a tokenizer) within ~5–10% on English/code text.
|
|
382
|
-
*
|
|
383
|
-
* This materializes the native BPE table (~50MB RSS) on first call. Use it
|
|
384
|
-
* only for context-changing decisions (compaction trigger/cut points, pruning
|
|
385
|
-
* budgets, branch summarization, fork-context seeding, context-limit
|
|
386
|
-
* enforcement). For display-only totals use
|
|
387
|
-
* {@link estimateMessageTokensHeuristic}.
|
|
388
|
-
*/
|
|
389
|
-
export function countMessageTokensNativeO200k(message: AgentMessage): number {
|
|
390
|
-
return countCollectedMessageFragments(collectMessageFragments(message));
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Backwards-compatible alias for {@link countMessageTokensNativeO200k}.
|
|
395
|
-
* Existing callers treat this as the canonical message-token estimator for
|
|
396
|
-
* context-changing decisions.
|
|
397
|
-
*/
|
|
398
|
-
export const estimateTokens = countMessageTokensNativeO200k;
|
|
399
|
-
|
|
400
330
|
/**
|
|
401
331
|
* Average bytes per token for the cheap heuristic. ~4 bytes/token is the
|
|
402
332
|
* conventional approximation for English/code text under modern BPE
|
|
@@ -406,10 +336,11 @@ export const estimateTokens = countMessageTokensNativeO200k;
|
|
|
406
336
|
const HEURISTIC_BYTES_PER_TOKEN = 4;
|
|
407
337
|
|
|
408
338
|
/**
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
339
|
+
* Native-free chars/4 token estimate for a message. This is the only message
|
|
340
|
+
* token estimator: provider usage (see {@link calculatePromptTokens}) anchors
|
|
341
|
+
* the already-sent context, and this covers unsent/trailing deltas, per-entry
|
|
342
|
+
* budgeting, and display surfaces. Callers add a conservative inflation factor
|
|
343
|
+
* where compaction-threshold safety requires it.
|
|
413
344
|
*/
|
|
414
345
|
export function estimateMessageTokensHeuristic(message: AgentMessage): number {
|
|
415
346
|
const { fragments, extra } = collectMessageFragments(message);
|
|
@@ -421,8 +352,8 @@ export function estimateMessageTokensHeuristic(message: AgentMessage): number {
|
|
|
421
352
|
}
|
|
422
353
|
|
|
423
354
|
/**
|
|
424
|
-
*
|
|
425
|
-
* counterpart of
|
|
355
|
+
* Native-free chars/4 token estimate for plain string fragments. Fragment-level
|
|
356
|
+
* counterpart of {@link estimateMessageTokensHeuristic}.
|
|
426
357
|
*/
|
|
427
358
|
export function estimateTextTokensHeuristic(fragments: string | readonly string[]): number {
|
|
428
359
|
if (typeof fragments === "string") return Math.ceil(fragments.length / HEURISTIC_BYTES_PER_TOKEN);
|