@oh-my-pi/pi-agent-core 18.0.10 → 18.0.11
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 +6 -0
- package/dist/types/tokenizer.d.ts +8 -8
- package/package.json +8 -8
- package/src/tokenizer.ts +48 -23
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { Model } from "@oh-my-pi/pi-ai";
|
|
2
|
-
import
|
|
2
|
+
import * as natives from "@oh-my-pi/pi-natives";
|
|
3
3
|
import type { AgentMessage } from "./types.js";
|
|
4
4
|
/** Maps the catalog-resolved tokenizer family to its native implementation. */
|
|
5
|
-
export declare function tokenizerEncodingForModel(model: Pick<Model, "tokenizer"> | null | undefined): Encoding | null;
|
|
5
|
+
export declare function tokenizerEncodingForModel(model: Pick<Model, "tokenizer"> | null | undefined): natives.Encoding | null;
|
|
6
6
|
/**
|
|
7
|
-
* `strict` always
|
|
8
|
-
*
|
|
7
|
+
* `strict` always tries an exact native count (the catalog-resolved tokenizer
|
|
8
|
+
* when known, o200k_base otherwise), falling back to the byte upper bound when
|
|
9
|
+
* the loaded addon does not recognize that encoding. `approximate` and
|
|
9
10
|
* `upperbound` prefer the same exact count for known tokenizer families or
|
|
10
11
|
* when `PI_TOKENIZER_ACCURATE=1` is set; otherwise they use a cheap heuristic:
|
|
11
12
|
* `approximate` a bytes/4 guess, `upperbound` the raw byte length (never
|
|
@@ -31,11 +32,10 @@ export interface TokenBudgetCheck {
|
|
|
31
32
|
fits: boolean;
|
|
32
33
|
/**
|
|
33
34
|
* Token count behind the verdict: the exact native count when `exact` is
|
|
34
|
-
* set, otherwise the
|
|
35
|
-
* only an over-estimate of a count known to be under budget).
|
|
35
|
+
* set, otherwise the conservative byte upper bound.
|
|
36
36
|
*/
|
|
37
37
|
tokens: number;
|
|
38
|
-
/** Whether
|
|
38
|
+
/** Whether `tokens` came from the exact native tokenizer. */
|
|
39
39
|
exact: boolean;
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
@@ -50,7 +50,7 @@ export interface TokenBudgetCheck {
|
|
|
50
50
|
export declare class Tokenizer {
|
|
51
51
|
#private;
|
|
52
52
|
constructor(model?: Pick<Model, "tokenizer"> | null);
|
|
53
|
-
get encoding(): Encoding | null;
|
|
53
|
+
get encoding(): natives.Encoding | null;
|
|
54
54
|
countTokens(text: string | string[], mode?: TokenCountMode): number;
|
|
55
55
|
/**
|
|
56
56
|
* Cheap-first budget probe — the way to ask "does this fit in `budget`
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-agent-core",
|
|
4
|
-
"version": "18.0.
|
|
4
|
+
"version": "18.0.11",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Stencil Labs, Inc.",
|
|
@@ -35,16 +35,16 @@
|
|
|
35
35
|
"fmt": "biome format --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@oh-my-pi/pi-ai": "18.0.
|
|
39
|
-
"@oh-my-pi/pi-catalog": "18.0.
|
|
40
|
-
"@oh-my-pi/pi-natives": "18.0.
|
|
41
|
-
"@oh-my-pi/pi-utils": "18.0.
|
|
42
|
-
"@oh-my-pi/pi-wire": "18.0.
|
|
43
|
-
"@oh-my-pi/snapcompact": "18.0.
|
|
38
|
+
"@oh-my-pi/pi-ai": "18.0.11",
|
|
39
|
+
"@oh-my-pi/pi-catalog": "18.0.11",
|
|
40
|
+
"@oh-my-pi/pi-natives": "18.0.11",
|
|
41
|
+
"@oh-my-pi/pi-utils": "18.0.11",
|
|
42
|
+
"@oh-my-pi/pi-wire": "18.0.11",
|
|
43
|
+
"@oh-my-pi/snapcompact": "18.0.11",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@oh-my-pi/omptype": "18.0.
|
|
47
|
+
"@oh-my-pi/omptype": "18.0.11",
|
|
48
48
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
|
49
49
|
"@opentelemetry/sdk-trace-base": "^2.9.0",
|
|
50
50
|
"@types/bun": "^1.3.14"
|
package/src/tokenizer.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Model } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import type { ModelTokenizer } from "@oh-my-pi/pi-catalog/types";
|
|
3
|
-
import
|
|
3
|
+
import * as natives from "@oh-my-pi/pi-natives";
|
|
4
4
|
import { stringifyJson } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import * as snapcompact from "@oh-my-pi/snapcompact";
|
|
6
6
|
import { isEstimateCacheable, messageEstimateVersion } from "./compaction/message-cache";
|
|
@@ -9,25 +9,26 @@ import type { AgentMessage } from "./types";
|
|
|
9
9
|
const testEnv = Bun.env.NODE_ENV === "test";
|
|
10
10
|
const accurate = process.env.PI_TOKENIZER_ACCURATE === "1" && !testEnv;
|
|
11
11
|
|
|
12
|
-
const NATIVE_ENCODING: Record<ModelTokenizer, Encoding> = {
|
|
13
|
-
"claude-v3": Encoding.ClaudeV3,
|
|
14
|
-
"claude-v47": Encoding.ClaudeV47,
|
|
15
|
-
"claude-v5": Encoding.ClaudeV5,
|
|
16
|
-
"claude-v5-sonnet": Encoding.ClaudeV5Sonnet,
|
|
17
|
-
qwen3: Encoding.Qwen3,
|
|
18
|
-
"deepseek-v3": Encoding.DeepSeekV3,
|
|
19
|
-
"kimi-k2": Encoding.KimiK2,
|
|
20
|
-
glm5: Encoding.Glm5,
|
|
12
|
+
const NATIVE_ENCODING: Record<ModelTokenizer, natives.Encoding> = {
|
|
13
|
+
"claude-v3": natives.Encoding.ClaudeV3,
|
|
14
|
+
"claude-v47": natives.Encoding.ClaudeV47,
|
|
15
|
+
"claude-v5": natives.Encoding.ClaudeV5,
|
|
16
|
+
"claude-v5-sonnet": natives.Encoding.ClaudeV5Sonnet,
|
|
17
|
+
qwen3: natives.Encoding.Qwen3,
|
|
18
|
+
"deepseek-v3": natives.Encoding.DeepSeekV3,
|
|
19
|
+
"kimi-k2": natives.Encoding.KimiK2,
|
|
20
|
+
glm5: natives.Encoding.Glm5,
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
/** Maps the catalog-resolved tokenizer family to its native implementation. */
|
|
24
|
-
export function tokenizerEncodingForModel(model: Pick<Model, "tokenizer"> | null | undefined): Encoding | null {
|
|
24
|
+
export function tokenizerEncodingForModel(model: Pick<Model, "tokenizer"> | null | undefined): natives.Encoding | null {
|
|
25
25
|
return model?.tokenizer ? NATIVE_ENCODING[model.tokenizer] : null;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* `strict` always
|
|
30
|
-
*
|
|
29
|
+
* `strict` always tries an exact native count (the catalog-resolved tokenizer
|
|
30
|
+
* when known, o200k_base otherwise), falling back to the byte upper bound when
|
|
31
|
+
* the loaded addon does not recognize that encoding. `approximate` and
|
|
31
32
|
* `upperbound` prefer the same exact count for known tokenizer families or
|
|
32
33
|
* when `PI_TOKENIZER_ACCURATE=1` is set; otherwise they use a cheap heuristic:
|
|
33
34
|
* `approximate` a bytes/4 guess, `upperbound` the raw byte length (never
|
|
@@ -61,17 +62,41 @@ function sumFragments(text: string | string[], perFragment: (t: string) => numbe
|
|
|
61
62
|
return Array.isArray(text) ? text.reduce((sum, t) => sum + perFragment(t), 0) : perFragment(text);
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
interface NativeTokenCount {
|
|
66
|
+
tokens: number;
|
|
67
|
+
exact: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function countTokensNat(
|
|
71
|
+
text: string | string[],
|
|
72
|
+
encoding: natives.Encoding | null | undefined,
|
|
73
|
+
mode: TokenCountMode,
|
|
74
|
+
): NativeTokenCount {
|
|
75
|
+
try {
|
|
76
|
+
return { tokens: natives.countTokens(text, encoding), exact: true };
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (
|
|
79
|
+
!(error instanceof Error) ||
|
|
80
|
+
(!error.message.includes("does not match any variant of enum") &&
|
|
81
|
+
!error.message.includes("unknown enum variant"))
|
|
82
|
+
) {
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
const tokens = sumFragments(text, mode === "approximate" ? byteEstimate : byteLength);
|
|
86
|
+
return { tokens, exact: false };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
64
90
|
/** Verdict from {@link Tokenizer.checkTokenBudget}. */
|
|
65
91
|
export interface TokenBudgetCheck {
|
|
66
92
|
/** Whether the text fits the budget. */
|
|
67
93
|
fits: boolean;
|
|
68
94
|
/**
|
|
69
95
|
* Token count behind the verdict: the exact native count when `exact` is
|
|
70
|
-
* set, otherwise the
|
|
71
|
-
* only an over-estimate of a count known to be under budget).
|
|
96
|
+
* set, otherwise the conservative byte upper bound.
|
|
72
97
|
*/
|
|
73
98
|
tokens: number;
|
|
74
|
-
/** Whether
|
|
99
|
+
/** Whether `tokens` came from the exact native tokenizer. */
|
|
75
100
|
exact: boolean;
|
|
76
101
|
}
|
|
77
102
|
|
|
@@ -104,7 +129,7 @@ interface MessageEstimate {
|
|
|
104
129
|
* `PI_TOKENIZER_ACCURATE=1`).
|
|
105
130
|
*/
|
|
106
131
|
export class Tokenizer {
|
|
107
|
-
readonly #encoding: Encoding | null;
|
|
132
|
+
readonly #encoding: natives.Encoding | null;
|
|
108
133
|
|
|
109
134
|
/**
|
|
110
135
|
* Per-message estimate memo. Keyed by message identity, deliberately not a
|
|
@@ -120,14 +145,14 @@ export class Tokenizer {
|
|
|
120
145
|
this.#encoding = tokenizerEncodingForModel(model);
|
|
121
146
|
}
|
|
122
147
|
|
|
123
|
-
get encoding(): Encoding | null {
|
|
148
|
+
get encoding(): natives.Encoding | null {
|
|
124
149
|
return this.#encoding;
|
|
125
150
|
}
|
|
126
151
|
|
|
127
152
|
countTokens(text: string | string[], mode: TokenCountMode = "approximate"): number {
|
|
128
|
-
if (mode === "strict") return countTokensNat(text, this.#encoding);
|
|
129
|
-
if (!testEnv && this.#encoding !== null) return countTokensNat(text, this.#encoding);
|
|
130
|
-
if (accurate) return countTokensNat(text);
|
|
153
|
+
if (mode === "strict") return countTokensNat(text, this.#encoding, mode).tokens;
|
|
154
|
+
if (!testEnv && this.#encoding !== null) return countTokensNat(text, this.#encoding, mode).tokens;
|
|
155
|
+
if (accurate) return countTokensNat(text, undefined, mode).tokens;
|
|
131
156
|
return sumFragments(text, mode === "upperbound" ? byteLength : byteEstimate);
|
|
132
157
|
}
|
|
133
158
|
|
|
@@ -145,8 +170,8 @@ export class Tokenizer {
|
|
|
145
170
|
checkTokenBudget(text: string | string[], budget: number): TokenBudgetCheck {
|
|
146
171
|
const bound = sumFragments(text, byteLength);
|
|
147
172
|
if (bound <= budget) return { fits: true, tokens: bound, exact: false };
|
|
148
|
-
const
|
|
149
|
-
return { fits: tokens <= budget, tokens, exact:
|
|
173
|
+
const result = countTokensNat(text, this.#encoding, "strict");
|
|
174
|
+
return { fits: result.tokens <= budget, tokens: result.tokens, exact: result.exact };
|
|
150
175
|
}
|
|
151
176
|
|
|
152
177
|
/**
|