@graphorin/provider 0.5.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/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.js +6 -0
- package/dist/adapters/llamacpp-server.d.ts +69 -0
- package/dist/adapters/llamacpp-server.d.ts.map +1 -0
- package/dist/adapters/llamacpp-server.js +51 -0
- package/dist/adapters/llamacpp-server.js.map +1 -0
- package/dist/adapters/ollama.d.ts +43 -0
- package/dist/adapters/ollama.d.ts.map +1 -0
- package/dist/adapters/ollama.js +246 -0
- package/dist/adapters/ollama.js.map +1 -0
- package/dist/adapters/openai-compatible.d.ts +48 -0
- package/dist/adapters/openai-compatible.d.ts.map +1 -0
- package/dist/adapters/openai-compatible.js +28 -0
- package/dist/adapters/openai-compatible.js.map +1 -0
- package/dist/adapters/vercel.d.ts +135 -0
- package/dist/adapters/vercel.d.ts.map +1 -0
- package/dist/adapters/vercel.js +306 -0
- package/dist/adapters/vercel.js.map +1 -0
- package/dist/counters/anthropic.d.ts +36 -0
- package/dist/counters/anthropic.d.ts.map +1 -0
- package/dist/counters/anthropic.js +63 -0
- package/dist/counters/anthropic.js.map +1 -0
- package/dist/counters/bedrock.d.ts +27 -0
- package/dist/counters/bedrock.d.ts.map +1 -0
- package/dist/counters/bedrock.js +29 -0
- package/dist/counters/bedrock.js.map +1 -0
- package/dist/counters/dispatcher.d.ts +34 -0
- package/dist/counters/dispatcher.d.ts.map +1 -0
- package/dist/counters/dispatcher.js +78 -0
- package/dist/counters/dispatcher.js.map +1 -0
- package/dist/counters/global.d.ts +26 -0
- package/dist/counters/global.d.ts.map +1 -0
- package/dist/counters/global.js +31 -0
- package/dist/counters/global.js.map +1 -0
- package/dist/counters/google.d.ts +27 -0
- package/dist/counters/google.d.ts.map +1 -0
- package/dist/counters/google.js +29 -0
- package/dist/counters/google.js.map +1 -0
- package/dist/counters/heuristic.d.ts +37 -0
- package/dist/counters/heuristic.d.ts.map +1 -0
- package/dist/counters/heuristic.js +64 -0
- package/dist/counters/heuristic.js.map +1 -0
- package/dist/counters/index.d.ts +9 -0
- package/dist/counters/index.js +10 -0
- package/dist/counters/js-tiktoken.d.ts +63 -0
- package/dist/counters/js-tiktoken.d.ts.map +1 -0
- package/dist/counters/js-tiktoken.js +73 -0
- package/dist/counters/js-tiktoken.js.map +1 -0
- package/dist/counters/serialize.d.ts +37 -0
- package/dist/counters/serialize.d.ts.map +1 -0
- package/dist/counters/serialize.js +57 -0
- package/dist/counters/serialize.js.map +1 -0
- package/dist/errors/errors.d.ts +167 -0
- package/dist/errors/errors.d.ts.map +1 -0
- package/dist/errors/errors.js +182 -0
- package/dist/errors/errors.js.map +1 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.js +3 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/abort.js +34 -0
- package/dist/internal/abort.js.map +1 -0
- package/dist/internal/http.js +163 -0
- package/dist/internal/http.js.map +1 -0
- package/dist/internal/openai-shaped.js +308 -0
- package/dist/internal/openai-shaped.js.map +1 -0
- package/dist/internal/sse.js +104 -0
- package/dist/internal/sse.js.map +1 -0
- package/dist/internal/url-utils.js +24 -0
- package/dist/internal/url-utils.js.map +1 -0
- package/dist/middleware/compose.d.ts +85 -0
- package/dist/middleware/compose.d.ts.map +1 -0
- package/dist/middleware/compose.js +174 -0
- package/dist/middleware/compose.js.map +1 -0
- package/dist/middleware/index.d.ts +10 -0
- package/dist/middleware/index.js +11 -0
- package/dist/middleware/production-hook.d.ts +27 -0
- package/dist/middleware/production-hook.d.ts.map +1 -0
- package/dist/middleware/production-hook.js +21 -0
- package/dist/middleware/production-hook.js.map +1 -0
- package/dist/middleware/with-cost-limit.d.ts +46 -0
- package/dist/middleware/with-cost-limit.d.ts.map +1 -0
- package/dist/middleware/with-cost-limit.js +66 -0
- package/dist/middleware/with-cost-limit.js.map +1 -0
- package/dist/middleware/with-cost-tracking.d.ts +91 -0
- package/dist/middleware/with-cost-tracking.d.ts.map +1 -0
- package/dist/middleware/with-cost-tracking.js +95 -0
- package/dist/middleware/with-cost-tracking.js.map +1 -0
- package/dist/middleware/with-fallback.d.ts +25 -0
- package/dist/middleware/with-fallback.d.ts.map +1 -0
- package/dist/middleware/with-fallback.js +84 -0
- package/dist/middleware/with-fallback.js.map +1 -0
- package/dist/middleware/with-rate-limit.d.ts +36 -0
- package/dist/middleware/with-rate-limit.d.ts.map +1 -0
- package/dist/middleware/with-rate-limit.js +141 -0
- package/dist/middleware/with-rate-limit.js.map +1 -0
- package/dist/middleware/with-redaction.d.ts +60 -0
- package/dist/middleware/with-redaction.d.ts.map +1 -0
- package/dist/middleware/with-redaction.js +305 -0
- package/dist/middleware/with-redaction.js.map +1 -0
- package/dist/middleware/with-retry.d.ts +26 -0
- package/dist/middleware/with-retry.d.ts.map +1 -0
- package/dist/middleware/with-retry.js +157 -0
- package/dist/middleware/with-retry.js.map +1 -0
- package/dist/middleware/with-tracing.d.ts +21 -0
- package/dist/middleware/with-tracing.d.ts.map +1 -0
- package/dist/middleware/with-tracing.js +55 -0
- package/dist/middleware/with-tracing.js.map +1 -0
- package/dist/model-tier/classify.d.ts +34 -0
- package/dist/model-tier/classify.d.ts.map +1 -0
- package/dist/model-tier/classify.js +117 -0
- package/dist/model-tier/classify.js.map +1 -0
- package/dist/model-tier/index.d.ts +2 -0
- package/dist/model-tier/index.js +3 -0
- package/dist/provider.d.ts +51 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +64 -0
- package/dist/provider.js.map +1 -0
- package/dist/reasoning/apply-policy.d.ts +22 -0
- package/dist/reasoning/apply-policy.d.ts.map +1 -0
- package/dist/reasoning/apply-policy.js +47 -0
- package/dist/reasoning/apply-policy.js.map +1 -0
- package/dist/reasoning/classify-contract.d.ts +41 -0
- package/dist/reasoning/classify-contract.d.ts.map +1 -0
- package/dist/reasoning/classify-contract.js +59 -0
- package/dist/reasoning/classify-contract.js.map +1 -0
- package/dist/reasoning/index.d.ts +4 -0
- package/dist/reasoning/index.js +5 -0
- package/dist/reasoning/retention.d.ts +39 -0
- package/dist/reasoning/retention.d.ts.map +1 -0
- package/dist/reasoning/retention.js +33 -0
- package/dist/reasoning/retention.js.map +1 -0
- package/dist/tool-examples.d.ts +14 -0
- package/dist/tool-examples.d.ts.map +1 -0
- package/dist/tool-examples.js +35 -0
- package/dist/tool-examples.js.map +1 -0
- package/dist/trust/classify-local-provider.d.ts +47 -0
- package/dist/trust/classify-local-provider.d.ts.map +1 -0
- package/dist/trust/classify-local-provider.js +164 -0
- package/dist/trust/classify-local-provider.js.map +1 -0
- package/dist/trust/index.d.ts +3 -0
- package/dist/trust/index.js +3 -0
- package/package.json +128 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Message, TokenCounter } from "@graphorin/core";
|
|
2
|
+
|
|
3
|
+
//#region src/counters/bedrock.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for {@link BedrockAPICounter}.
|
|
7
|
+
*
|
|
8
|
+
* @stable
|
|
9
|
+
*/
|
|
10
|
+
interface BedrockAPICounterOptions {
|
|
11
|
+
readonly modelId: string;
|
|
12
|
+
readonly id?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @stable
|
|
16
|
+
*/
|
|
17
|
+
declare class BedrockAPICounter implements TokenCounter {
|
|
18
|
+
#private;
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly version: string;
|
|
21
|
+
constructor(options: BedrockAPICounterOptions);
|
|
22
|
+
count(messages: ReadonlyArray<Message>): Promise<number>;
|
|
23
|
+
countText(text: string): Promise<number>;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { BedrockAPICounter, BedrockAPICounterOptions };
|
|
27
|
+
//# sourceMappingURL=bedrock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bedrock.d.ts","names":[],"sources":["../../src/counters/bedrock.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UAmBiB,wBAAA;;;;;;;cAQJ,iBAAA,YAA6B;;;;uBAKnB;kBAML,cAAc,WAAW;2BAIhB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { JsTiktokenCounter } from "./js-tiktoken.js";
|
|
2
|
+
|
|
3
|
+
//#region src/counters/bedrock.ts
|
|
4
|
+
/**
|
|
5
|
+
* @stable
|
|
6
|
+
*/
|
|
7
|
+
var BedrockAPICounter = class {
|
|
8
|
+
id;
|
|
9
|
+
version;
|
|
10
|
+
#fallback;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.#fallback = new JsTiktokenCounter({
|
|
13
|
+
encoding: "cl100k_base",
|
|
14
|
+
modelId: options.modelId
|
|
15
|
+
});
|
|
16
|
+
this.id = options.id ?? `bedrock-proxy@${options.modelId}`;
|
|
17
|
+
this.version = `bedrock-cl100k-proxy-${options.modelId}-v1`;
|
|
18
|
+
}
|
|
19
|
+
count(messages) {
|
|
20
|
+
return this.#fallback.count(messages);
|
|
21
|
+
}
|
|
22
|
+
countText(text) {
|
|
23
|
+
return this.#fallback.countText(text);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
export { BedrockAPICounter };
|
|
29
|
+
//# sourceMappingURL=bedrock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bedrock.js","names":["#fallback"],"sources":["../../src/counters/bedrock.ts"],"sourcesContent":["/**\n * `BedrockAPICounter` — placeholder counter that delegates to\n * `JsTiktokenCounter('cl100k_base')` for now (Bedrock's native\n * `count_tokens` API requires AWS Signature v4; full integration\n * lands in Phase 16). The proxy is sufficient for v0.1 within the\n * documented ±5% accuracy envelope.\n *\n * @packageDocumentation\n */\n\nimport type { Message, TokenCounter } from '@graphorin/core';\n\nimport { JsTiktokenCounter } from './js-tiktoken.js';\n\n/**\n * Options for {@link BedrockAPICounter}.\n *\n * @stable\n */\nexport interface BedrockAPICounterOptions {\n readonly modelId: string;\n readonly id?: string;\n}\n\n/**\n * @stable\n */\nexport class BedrockAPICounter implements TokenCounter {\n readonly id: string;\n readonly version: string;\n readonly #fallback: JsTiktokenCounter;\n\n constructor(options: BedrockAPICounterOptions) {\n this.#fallback = new JsTiktokenCounter({ encoding: 'cl100k_base', modelId: options.modelId });\n this.id = options.id ?? `bedrock-proxy@${options.modelId}`;\n this.version = `bedrock-cl100k-proxy-${options.modelId}-v1`;\n }\n\n count(messages: ReadonlyArray<Message>): Promise<number> {\n return this.#fallback.count(messages);\n }\n\n countText(text: string): Promise<number> {\n return this.#fallback.countText(text);\n }\n}\n"],"mappings":";;;;;;AA2BA,IAAa,oBAAb,MAAuD;CACrD,AAAS;CACT,AAAS;CACT,CAASA;CAET,YAAY,SAAmC;AAC7C,QAAKA,WAAY,IAAI,kBAAkB;GAAE,UAAU;GAAe,SAAS,QAAQ;GAAS,CAAC;AAC7F,OAAK,KAAK,QAAQ,MAAM,iBAAiB,QAAQ;AACjD,OAAK,UAAU,wBAAwB,QAAQ,QAAQ;;CAGzD,MAAM,UAAmD;AACvD,SAAO,MAAKA,SAAU,MAAM,SAAS;;CAGvC,UAAU,MAA+B;AACvC,SAAO,MAAKA,SAAU,UAAU,KAAK"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { TokenCounter } from "@graphorin/core";
|
|
2
|
+
|
|
3
|
+
//#region src/counters/dispatcher.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for {@link createDefaultCounter}.
|
|
7
|
+
*
|
|
8
|
+
* @stable
|
|
9
|
+
*/
|
|
10
|
+
interface CreateDefaultCounterOptions {
|
|
11
|
+
/** Concrete model id (e.g. `'gpt-4o'`, `'claude-opus-4-7'`). */
|
|
12
|
+
readonly model: string;
|
|
13
|
+
/** Optional provider hint to short-circuit the regex matching. */
|
|
14
|
+
readonly provider?: string;
|
|
15
|
+
/** Optional Anthropic API key threaded through to {@link AnthropicAPICounter}. */
|
|
16
|
+
readonly anthropicApiKey?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build the recommended {@link TokenCounter} for the given
|
|
20
|
+
* `(provider, model)` pair. The dispatch table:
|
|
21
|
+
*
|
|
22
|
+
* - Anthropic Claude → `AnthropicAPICounter` (native if `apiKey` set,
|
|
23
|
+
* otherwise `cl100k_base` proxy).
|
|
24
|
+
* - OpenAI / OpenAI-compatible → `JsTiktokenCounter('cl100k_base')`.
|
|
25
|
+
* - Google Gemini → `GoogleAPICounter` (cl100k_base proxy in v0.1).
|
|
26
|
+
* - Bedrock Claude → `BedrockAPICounter` (cl100k_base proxy in v0.1).
|
|
27
|
+
* - Ollama / unknown → `HeuristicCounter` with one WARN per process.
|
|
28
|
+
*
|
|
29
|
+
* @stable
|
|
30
|
+
*/
|
|
31
|
+
declare function createDefaultCounter(options: CreateDefaultCounterOptions): TokenCounter;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { CreateDefaultCounterOptions, createDefaultCounter };
|
|
34
|
+
//# sourceMappingURL=dispatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","names":[],"sources":["../../src/counters/dispatcher.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UAuBiB,2BAAA;;;;;;;;;;;;;;;;;;;;;iBAuDD,oBAAA,UAA8B,8BAA8B"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { JsTiktokenCounter } from "./js-tiktoken.js";
|
|
2
|
+
import { AnthropicAPICounter } from "./anthropic.js";
|
|
3
|
+
import { BedrockAPICounter } from "./bedrock.js";
|
|
4
|
+
import { GoogleAPICounter } from "./google.js";
|
|
5
|
+
import { HeuristicCounter } from "./heuristic.js";
|
|
6
|
+
|
|
7
|
+
//#region src/counters/dispatcher.ts
|
|
8
|
+
/**
|
|
9
|
+
* Detect the provider family for a model id. Exposed for downstream
|
|
10
|
+
* consumers (the model-tier classifier; future per-tool counters) so
|
|
11
|
+
* provider-family detection is centralised here.
|
|
12
|
+
*
|
|
13
|
+
* @stable
|
|
14
|
+
*/
|
|
15
|
+
function detectProviderFamily(args) {
|
|
16
|
+
const provider = args.provider?.toLowerCase();
|
|
17
|
+
const modelLower = args.model.toLowerCase();
|
|
18
|
+
if (provider === "anthropic" || modelLower.startsWith("claude") || modelLower.startsWith("anthropic.claude")) {
|
|
19
|
+
if (provider === "bedrock" || modelLower.startsWith("anthropic.claude")) return "bedrock";
|
|
20
|
+
return "anthropic";
|
|
21
|
+
}
|
|
22
|
+
if (provider === "google" || modelLower.startsWith("gemini")) return "google";
|
|
23
|
+
if (provider === "bedrock") return "bedrock";
|
|
24
|
+
if (provider === "openai" || provider === "azure-openai" || /^(gpt-|o1|o3|o4)/.test(modelLower)) return "openai";
|
|
25
|
+
if (provider === "ollama" || modelLower.startsWith("ollama-")) return "ollama";
|
|
26
|
+
if (provider === "openai-compatible" || provider === "lmstudio" || provider === "vllm") return "openai-compatible";
|
|
27
|
+
return "unknown";
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build the recommended {@link TokenCounter} for the given
|
|
31
|
+
* `(provider, model)` pair. The dispatch table:
|
|
32
|
+
*
|
|
33
|
+
* - Anthropic Claude → `AnthropicAPICounter` (native if `apiKey` set,
|
|
34
|
+
* otherwise `cl100k_base` proxy).
|
|
35
|
+
* - OpenAI / OpenAI-compatible → `JsTiktokenCounter('cl100k_base')`.
|
|
36
|
+
* - Google Gemini → `GoogleAPICounter` (cl100k_base proxy in v0.1).
|
|
37
|
+
* - Bedrock Claude → `BedrockAPICounter` (cl100k_base proxy in v0.1).
|
|
38
|
+
* - Ollama / unknown → `HeuristicCounter` with one WARN per process.
|
|
39
|
+
*
|
|
40
|
+
* @stable
|
|
41
|
+
*/
|
|
42
|
+
function createDefaultCounter(options) {
|
|
43
|
+
switch (detectProviderFamily({
|
|
44
|
+
model: options.model,
|
|
45
|
+
...options.provider !== void 0 ? { provider: options.provider } : {}
|
|
46
|
+
})) {
|
|
47
|
+
case "anthropic": return new AnthropicAPICounter({
|
|
48
|
+
modelId: options.model,
|
|
49
|
+
...options.anthropicApiKey !== void 0 ? { apiKey: options.anthropicApiKey } : {}
|
|
50
|
+
});
|
|
51
|
+
case "bedrock": return new BedrockAPICounter({ modelId: options.model });
|
|
52
|
+
case "google": return new GoogleAPICounter({ modelId: options.model });
|
|
53
|
+
case "openai":
|
|
54
|
+
case "openai-compatible": return new JsTiktokenCounter({
|
|
55
|
+
encoding: defaultOpenAiEncoding(options.model),
|
|
56
|
+
modelId: options.model
|
|
57
|
+
});
|
|
58
|
+
default: return new HeuristicCounter({ modelId: options.model });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* PS-20: pick the fallback tiktoken encoding by model family. `js-tiktoken`'s
|
|
63
|
+
* `encodingForModel` handles ids it recognises; this is the explicit fallback
|
|
64
|
+
* for ids it does not (the 2025/2026 families). gpt-4o / gpt-4.1 / gpt-5+ and
|
|
65
|
+
* the o-series reasoning models use `o200k_base`; legacy gpt-4 / gpt-3.5 stay
|
|
66
|
+
* on `cl100k_base`.
|
|
67
|
+
*/
|
|
68
|
+
function defaultOpenAiEncoding(model) {
|
|
69
|
+
if (model === void 0) return "cl100k_base";
|
|
70
|
+
const id = model.toLowerCase();
|
|
71
|
+
const sep = Math.max(id.lastIndexOf("/"), id.lastIndexOf(":"));
|
|
72
|
+
const bare = sep === -1 ? id : id.slice(sep + 1);
|
|
73
|
+
return /^(?:gpt-4o|gpt-4\.1|gpt-[5-9]|gpt-\d{2}|o[1-9])/.test(bare) ? "o200k_base" : "cl100k_base";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { createDefaultCounter };
|
|
78
|
+
//# sourceMappingURL=dispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher.js","names":[],"sources":["../../src/counters/dispatcher.ts"],"sourcesContent":["/**\n * `createDefaultCounter` — model-id classifier that returns the\n * recommended {@link TokenCounter} for a given `(provider, model)`\n * pair. The same classifier is reused by the model-tier dispatcher\n * (`@graphorin/provider/model-tier`) so provider-family detection\n * lives in exactly one place.\n *\n * @packageDocumentation\n */\n\nimport type { TokenCounter } from '@graphorin/core';\n\nimport { AnthropicAPICounter } from './anthropic.js';\nimport { BedrockAPICounter } from './bedrock.js';\nimport { GoogleAPICounter } from './google.js';\nimport { HeuristicCounter } from './heuristic.js';\nimport { JsTiktokenCounter } from './js-tiktoken.js';\n\n/**\n * Options for {@link createDefaultCounter}.\n *\n * @stable\n */\nexport interface CreateDefaultCounterOptions {\n /** Concrete model id (e.g. `'gpt-4o'`, `'claude-opus-4-7'`). */\n readonly model: string;\n /** Optional provider hint to short-circuit the regex matching. */\n readonly provider?: string;\n /** Optional Anthropic API key threaded through to {@link AnthropicAPICounter}. */\n readonly anthropicApiKey?: string;\n}\n\n/**\n * Detect the provider family for a model id. Exposed for downstream\n * consumers (the model-tier classifier; future per-tool counters) so\n * provider-family detection is centralised here.\n *\n * @stable\n */\nexport function detectProviderFamily(args: {\n readonly model: string;\n readonly provider?: string;\n}): 'anthropic' | 'openai' | 'google' | 'bedrock' | 'ollama' | 'openai-compatible' | 'unknown' {\n const provider = args.provider?.toLowerCase();\n const modelLower = args.model.toLowerCase();\n if (\n provider === 'anthropic' ||\n modelLower.startsWith('claude') ||\n modelLower.startsWith('anthropic.claude')\n ) {\n if (provider === 'bedrock' || modelLower.startsWith('anthropic.claude')) return 'bedrock';\n return 'anthropic';\n }\n if (provider === 'google' || modelLower.startsWith('gemini')) return 'google';\n if (provider === 'bedrock') return 'bedrock';\n if (provider === 'openai' || provider === 'azure-openai' || /^(gpt-|o1|o3|o4)/.test(modelLower)) {\n return 'openai';\n }\n if (provider === 'ollama' || modelLower.startsWith('ollama-')) return 'ollama';\n if (provider === 'openai-compatible' || provider === 'lmstudio' || provider === 'vllm') {\n return 'openai-compatible';\n }\n return 'unknown';\n}\n\n/**\n * Build the recommended {@link TokenCounter} for the given\n * `(provider, model)` pair. The dispatch table:\n *\n * - Anthropic Claude → `AnthropicAPICounter` (native if `apiKey` set,\n * otherwise `cl100k_base` proxy).\n * - OpenAI / OpenAI-compatible → `JsTiktokenCounter('cl100k_base')`.\n * - Google Gemini → `GoogleAPICounter` (cl100k_base proxy in v0.1).\n * - Bedrock Claude → `BedrockAPICounter` (cl100k_base proxy in v0.1).\n * - Ollama / unknown → `HeuristicCounter` with one WARN per process.\n *\n * @stable\n */\nexport function createDefaultCounter(options: CreateDefaultCounterOptions): TokenCounter {\n const family = detectProviderFamily({\n model: options.model,\n ...(options.provider !== undefined ? { provider: options.provider } : {}),\n });\n switch (family) {\n case 'anthropic':\n return new AnthropicAPICounter({\n modelId: options.model,\n ...(options.anthropicApiKey !== undefined ? { apiKey: options.anthropicApiKey } : {}),\n });\n case 'bedrock':\n return new BedrockAPICounter({ modelId: options.model });\n case 'google':\n return new GoogleAPICounter({ modelId: options.model });\n case 'openai':\n case 'openai-compatible':\n return new JsTiktokenCounter({\n encoding: defaultOpenAiEncoding(options.model),\n modelId: options.model,\n });\n default:\n return new HeuristicCounter({ modelId: options.model });\n }\n}\n\n/**\n * PS-20: pick the fallback tiktoken encoding by model family. `js-tiktoken`'s\n * `encodingForModel` handles ids it recognises; this is the explicit fallback\n * for ids it does not (the 2025/2026 families). gpt-4o / gpt-4.1 / gpt-5+ and\n * the o-series reasoning models use `o200k_base`; legacy gpt-4 / gpt-3.5 stay\n * on `cl100k_base`.\n */\nexport function defaultOpenAiEncoding(model: string | undefined): 'o200k_base' | 'cl100k_base' {\n if (model === undefined) return 'cl100k_base';\n const id = model.toLowerCase();\n const sep = Math.max(id.lastIndexOf('/'), id.lastIndexOf(':'));\n const bare = sep === -1 ? id : id.slice(sep + 1);\n return /^(?:gpt-4o|gpt-4\\.1|gpt-[5-9]|gpt-\\d{2}|o[1-9])/.test(bare)\n ? 'o200k_base'\n : 'cl100k_base';\n}\n"],"mappings":";;;;;;;;;;;;;;AAuCA,SAAgB,qBAAqB,MAG0D;CAC7F,MAAM,WAAW,KAAK,UAAU,aAAa;CAC7C,MAAM,aAAa,KAAK,MAAM,aAAa;AAC3C,KACE,aAAa,eACb,WAAW,WAAW,SAAS,IAC/B,WAAW,WAAW,mBAAmB,EACzC;AACA,MAAI,aAAa,aAAa,WAAW,WAAW,mBAAmB,CAAE,QAAO;AAChF,SAAO;;AAET,KAAI,aAAa,YAAY,WAAW,WAAW,SAAS,CAAE,QAAO;AACrE,KAAI,aAAa,UAAW,QAAO;AACnC,KAAI,aAAa,YAAY,aAAa,kBAAkB,mBAAmB,KAAK,WAAW,CAC7F,QAAO;AAET,KAAI,aAAa,YAAY,WAAW,WAAW,UAAU,CAAE,QAAO;AACtE,KAAI,aAAa,uBAAuB,aAAa,cAAc,aAAa,OAC9E,QAAO;AAET,QAAO;;;;;;;;;;;;;;;AAgBT,SAAgB,qBAAqB,SAAoD;AAKvF,SAJe,qBAAqB;EAClC,OAAO,QAAQ;EACf,GAAI,QAAQ,aAAa,SAAY,EAAE,UAAU,QAAQ,UAAU,GAAG,EAAE;EACzE,CAAC,EACF;EACE,KAAK,YACH,QAAO,IAAI,oBAAoB;GAC7B,SAAS,QAAQ;GACjB,GAAI,QAAQ,oBAAoB,SAAY,EAAE,QAAQ,QAAQ,iBAAiB,GAAG,EAAE;GACrF,CAAC;EACJ,KAAK,UACH,QAAO,IAAI,kBAAkB,EAAE,SAAS,QAAQ,OAAO,CAAC;EAC1D,KAAK,SACH,QAAO,IAAI,iBAAiB,EAAE,SAAS,QAAQ,OAAO,CAAC;EACzD,KAAK;EACL,KAAK,oBACH,QAAO,IAAI,kBAAkB;GAC3B,UAAU,sBAAsB,QAAQ,MAAM;GAC9C,SAAS,QAAQ;GAClB,CAAC;EACJ,QACE,QAAO,IAAI,iBAAiB,EAAE,SAAS,QAAQ,OAAO,CAAC;;;;;;;;;;AAW7D,SAAgB,sBAAsB,OAAyD;AAC7F,KAAI,UAAU,OAAW,QAAO;CAChC,MAAM,KAAK,MAAM,aAAa;CAC9B,MAAM,MAAM,KAAK,IAAI,GAAG,YAAY,IAAI,EAAE,GAAG,YAAY,IAAI,CAAC;CAC9D,MAAM,OAAO,QAAQ,KAAK,KAAK,GAAG,MAAM,MAAM,EAAE;AAChD,QAAO,kDAAkD,KAAK,KAAK,GAC/D,eACA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { TokenCounter } from "@graphorin/core";
|
|
2
|
+
|
|
3
|
+
//#region src/counters/global.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Set the process-global counter. Called once at startup by user
|
|
7
|
+
* code; passing `null` clears the slot.
|
|
8
|
+
*
|
|
9
|
+
* @stable
|
|
10
|
+
*/
|
|
11
|
+
declare function setGlobalTokenCounter(counter: TokenCounter | null): void;
|
|
12
|
+
/**
|
|
13
|
+
* Read the process-global counter, or `null` if unset.
|
|
14
|
+
*
|
|
15
|
+
* @stable
|
|
16
|
+
*/
|
|
17
|
+
declare function getGlobalTokenCounter(): TokenCounter | null;
|
|
18
|
+
/**
|
|
19
|
+
* Test-only hook that resets the global slot to `null` between cases.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
declare function __resetGlobalTokenCounter(): void;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { __resetGlobalTokenCounter, getGlobalTokenCounter, setGlobalTokenCounter };
|
|
26
|
+
//# sourceMappingURL=global.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global.d.ts","names":[],"sources":["../../src/counters/global.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;iBAkBgB,qBAAA,UAA+B;;;;;;iBAS/B,qBAAA,CAAA,GAAyB;;;;;;iBASzB,yBAAA,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/counters/global.ts
|
|
2
|
+
let globalCounter = null;
|
|
3
|
+
/**
|
|
4
|
+
* Set the process-global counter. Called once at startup by user
|
|
5
|
+
* code; passing `null` clears the slot.
|
|
6
|
+
*
|
|
7
|
+
* @stable
|
|
8
|
+
*/
|
|
9
|
+
function setGlobalTokenCounter(counter) {
|
|
10
|
+
globalCounter = counter;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Read the process-global counter, or `null` if unset.
|
|
14
|
+
*
|
|
15
|
+
* @stable
|
|
16
|
+
*/
|
|
17
|
+
function getGlobalTokenCounter() {
|
|
18
|
+
return globalCounter;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Test-only hook that resets the global slot to `null` between cases.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
function __resetGlobalTokenCounter() {
|
|
26
|
+
globalCounter = null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
export { __resetGlobalTokenCounter, getGlobalTokenCounter, setGlobalTokenCounter };
|
|
31
|
+
//# sourceMappingURL=global.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global.js","names":["globalCounter: TokenCounter | null"],"sources":["../../src/counters/global.ts"],"sourcesContent":["/**\n * Process-global token counter slot. Components (e.g. the\n * ContextEngine in Phase 10) read the global counter when no\n * per-provider counter is wired explicitly.\n *\n * @packageDocumentation\n */\n\nimport type { TokenCounter } from '@graphorin/core';\n\nlet globalCounter: TokenCounter | null = null;\n\n/**\n * Set the process-global counter. Called once at startup by user\n * code; passing `null` clears the slot.\n *\n * @stable\n */\nexport function setGlobalTokenCounter(counter: TokenCounter | null): void {\n globalCounter = counter;\n}\n\n/**\n * Read the process-global counter, or `null` if unset.\n *\n * @stable\n */\nexport function getGlobalTokenCounter(): TokenCounter | null {\n return globalCounter;\n}\n\n/**\n * Test-only hook that resets the global slot to `null` between cases.\n *\n * @internal\n */\nexport function __resetGlobalTokenCounter(): void {\n globalCounter = null;\n}\n"],"mappings":";AAUA,IAAIA,gBAAqC;;;;;;;AAQzC,SAAgB,sBAAsB,SAAoC;AACxE,iBAAgB;;;;;;;AAQlB,SAAgB,wBAA6C;AAC3D,QAAO;;;;;;;AAQT,SAAgB,4BAAkC;AAChD,iBAAgB"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Message, TokenCounter } from "@graphorin/core";
|
|
2
|
+
|
|
3
|
+
//#region src/counters/google.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for {@link GoogleAPICounter}.
|
|
7
|
+
*
|
|
8
|
+
* @stable
|
|
9
|
+
*/
|
|
10
|
+
interface GoogleAPICounterOptions {
|
|
11
|
+
readonly modelId: string;
|
|
12
|
+
readonly id?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @stable
|
|
16
|
+
*/
|
|
17
|
+
declare class GoogleAPICounter implements TokenCounter {
|
|
18
|
+
#private;
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly version: string;
|
|
21
|
+
constructor(options: GoogleAPICounterOptions);
|
|
22
|
+
count(messages: ReadonlyArray<Message>): Promise<number>;
|
|
23
|
+
countText(text: string): Promise<number>;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { GoogleAPICounter, GoogleAPICounterOptions };
|
|
27
|
+
//# sourceMappingURL=google.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google.d.ts","names":[],"sources":["../../src/counters/google.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UAmBiB,uBAAA;;;;;;;cAQJ,gBAAA,YAA4B;;;;uBAKlB;kBAML,cAAc,WAAW;2BAIhB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { JsTiktokenCounter } from "./js-tiktoken.js";
|
|
2
|
+
|
|
3
|
+
//#region src/counters/google.ts
|
|
4
|
+
/**
|
|
5
|
+
* @stable
|
|
6
|
+
*/
|
|
7
|
+
var GoogleAPICounter = class {
|
|
8
|
+
id;
|
|
9
|
+
version;
|
|
10
|
+
#fallback;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.#fallback = new JsTiktokenCounter({
|
|
13
|
+
encoding: "cl100k_base",
|
|
14
|
+
modelId: options.modelId
|
|
15
|
+
});
|
|
16
|
+
this.id = options.id ?? `google-proxy@${options.modelId}`;
|
|
17
|
+
this.version = `google-cl100k-proxy-${options.modelId}-v1`;
|
|
18
|
+
}
|
|
19
|
+
count(messages) {
|
|
20
|
+
return this.#fallback.count(messages);
|
|
21
|
+
}
|
|
22
|
+
countText(text) {
|
|
23
|
+
return this.#fallback.countText(text);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
export { GoogleAPICounter };
|
|
29
|
+
//# sourceMappingURL=google.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google.js","names":["#fallback"],"sources":["../../src/counters/google.ts"],"sourcesContent":["/**\n * `GoogleAPICounter` — placeholder counter that delegates to\n * `JsTiktokenCounter('cl100k_base')` for now. The full Google `:countTokens`\n * implementation lands in Phase 16 alongside the optional pricing\n * refresher; for v0.1 the proxy is sufficient (verified within ±5% on\n * the standard fixture corpus per the existing DEC-131 acceptance).\n *\n * @packageDocumentation\n */\n\nimport type { Message, TokenCounter } from '@graphorin/core';\n\nimport { JsTiktokenCounter } from './js-tiktoken.js';\n\n/**\n * Options for {@link GoogleAPICounter}.\n *\n * @stable\n */\nexport interface GoogleAPICounterOptions {\n readonly modelId: string;\n readonly id?: string;\n}\n\n/**\n * @stable\n */\nexport class GoogleAPICounter implements TokenCounter {\n readonly id: string;\n readonly version: string;\n readonly #fallback: JsTiktokenCounter;\n\n constructor(options: GoogleAPICounterOptions) {\n this.#fallback = new JsTiktokenCounter({ encoding: 'cl100k_base', modelId: options.modelId });\n this.id = options.id ?? `google-proxy@${options.modelId}`;\n this.version = `google-cl100k-proxy-${options.modelId}-v1`;\n }\n\n count(messages: ReadonlyArray<Message>): Promise<number> {\n return this.#fallback.count(messages);\n }\n\n countText(text: string): Promise<number> {\n return this.#fallback.countText(text);\n }\n}\n"],"mappings":";;;;;;AA2BA,IAAa,mBAAb,MAAsD;CACpD,AAAS;CACT,AAAS;CACT,CAASA;CAET,YAAY,SAAkC;AAC5C,QAAKA,WAAY,IAAI,kBAAkB;GAAE,UAAU;GAAe,SAAS,QAAQ;GAAS,CAAC;AAC7F,OAAK,KAAK,QAAQ,MAAM,gBAAgB,QAAQ;AAChD,OAAK,UAAU,uBAAuB,QAAQ,QAAQ;;CAGxD,MAAM,UAAmD;AACvD,SAAO,MAAKA,SAAU,MAAM,SAAS;;CAGvC,UAAU,MAA+B;AACvC,SAAO,MAAKA,SAAU,UAAU,KAAK"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Message, TokenCounter } from "@graphorin/core";
|
|
2
|
+
|
|
3
|
+
//#region src/counters/heuristic.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for {@link HeuristicCounter}.
|
|
7
|
+
*
|
|
8
|
+
* @stable
|
|
9
|
+
*/
|
|
10
|
+
interface HeuristicCounterOptions {
|
|
11
|
+
/** Average characters per token. Defaults to `4` (latin-1 estimate). */
|
|
12
|
+
readonly charsPerToken?: number;
|
|
13
|
+
/** Identifier carried on the produced counter. */
|
|
14
|
+
readonly id?: string;
|
|
15
|
+
/** Optional model hint surfaced in the WARN message. */
|
|
16
|
+
readonly modelId?: string;
|
|
17
|
+
/** Optional sink. Defaults to `console.warn`. */
|
|
18
|
+
readonly logger?: (message: string, meta?: object) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Counter that estimates tokens via simple character division. The
|
|
22
|
+
* estimator is deterministic and side-effect-free apart from the
|
|
23
|
+
* one-time WARN.
|
|
24
|
+
*
|
|
25
|
+
* @stable
|
|
26
|
+
*/
|
|
27
|
+
declare class HeuristicCounter implements TokenCounter {
|
|
28
|
+
#private;
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly version: string;
|
|
31
|
+
constructor(options?: HeuristicCounterOptions);
|
|
32
|
+
count(messages: ReadonlyArray<Message>): Promise<number>;
|
|
33
|
+
countText(text: string): Promise<number>;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { HeuristicCounter, HeuristicCounterOptions };
|
|
37
|
+
//# sourceMappingURL=heuristic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heuristic.d.ts","names":[],"sources":["../../src/counters/heuristic.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UAkBiB,uBAAA;;;;;;;;;;;;;;;;;cAoBJ,gBAAA,YAA4B;;;;wBAQlB;kBAYC,cAAc,WAAW;2BAUhB"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { serialiseMessageForCount, serializedToString } from "./serialize.js";
|
|
2
|
+
|
|
3
|
+
//#region src/counters/heuristic.ts
|
|
4
|
+
const WARNED = /* @__PURE__ */ new Set();
|
|
5
|
+
/**
|
|
6
|
+
* Counter that estimates tokens via simple character division. The
|
|
7
|
+
* estimator is deterministic and side-effect-free apart from the
|
|
8
|
+
* one-time WARN.
|
|
9
|
+
*
|
|
10
|
+
* @stable
|
|
11
|
+
*/
|
|
12
|
+
var HeuristicCounter = class {
|
|
13
|
+
id;
|
|
14
|
+
version;
|
|
15
|
+
#charsPerToken;
|
|
16
|
+
#modelId;
|
|
17
|
+
#logger;
|
|
18
|
+
#warnedOnce = false;
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
const charsPerToken = options.charsPerToken ?? 4;
|
|
21
|
+
if (!(charsPerToken > 0)) throw new RangeError("HeuristicCounter: charsPerToken must be > 0.");
|
|
22
|
+
this.#charsPerToken = charsPerToken;
|
|
23
|
+
this.id = options.id ?? `heuristic@${charsPerToken}cpt`;
|
|
24
|
+
this.version = `heuristic-${charsPerToken}-cpt-v1`;
|
|
25
|
+
if (options.modelId !== void 0) this.#modelId = options.modelId;
|
|
26
|
+
this.#logger = options.logger ?? defaultLogger;
|
|
27
|
+
}
|
|
28
|
+
async count(messages) {
|
|
29
|
+
this.#warnIfNeeded();
|
|
30
|
+
let total = 0;
|
|
31
|
+
for (const msg of messages) {
|
|
32
|
+
const serialised = serialiseMessageForCount(msg);
|
|
33
|
+
total += this.#estimate(serializedToString(serialised));
|
|
34
|
+
}
|
|
35
|
+
return total;
|
|
36
|
+
}
|
|
37
|
+
async countText(text) {
|
|
38
|
+
this.#warnIfNeeded();
|
|
39
|
+
return this.#estimate(text);
|
|
40
|
+
}
|
|
41
|
+
#estimate(text) {
|
|
42
|
+
if (text.length === 0) return 0;
|
|
43
|
+
return Math.ceil(text.length / this.#charsPerToken);
|
|
44
|
+
}
|
|
45
|
+
#warnIfNeeded() {
|
|
46
|
+
if (this.#warnedOnce) return;
|
|
47
|
+
const key = `${this.id}::${this.#modelId ?? "unknown"}`;
|
|
48
|
+
if (WARNED.has(key)) {
|
|
49
|
+
this.#warnedOnce = true;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
WARNED.add(key);
|
|
53
|
+
this.#warnedOnce = true;
|
|
54
|
+
this.#logger(`[graphorin/provider] heuristic token counter active (${this.id}); accuracy is approximate (~chars/${this.#charsPerToken}).`, this.#modelId !== void 0 ? { modelId: this.#modelId } : {});
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
function defaultLogger(message, meta) {
|
|
58
|
+
if (meta !== void 0) console.warn(message, meta);
|
|
59
|
+
else console.warn(message);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
export { HeuristicCounter };
|
|
64
|
+
//# sourceMappingURL=heuristic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heuristic.js","names":["#charsPerToken","#modelId","#logger","#warnIfNeeded","#estimate","#warnedOnce"],"sources":["../../src/counters/heuristic.ts"],"sourcesContent":["/**\n * Conservative `chars / N` token-count estimator. Used as the fallback\n * for unknown providers; emits exactly one WARN per unique\n * `(provider, model)` tuple per process so operators are nudged\n * toward a precise counter.\n *\n * @packageDocumentation\n */\n\nimport type { Message, TokenCounter } from '@graphorin/core';\n\nimport { serialiseMessageForCount, serializedToString } from './serialize.js';\n\n/**\n * Options for {@link HeuristicCounter}.\n *\n * @stable\n */\nexport interface HeuristicCounterOptions {\n /** Average characters per token. Defaults to `4` (latin-1 estimate). */\n readonly charsPerToken?: number;\n /** Identifier carried on the produced counter. */\n readonly id?: string;\n /** Optional model hint surfaced in the WARN message. */\n readonly modelId?: string;\n /** Optional sink. Defaults to `console.warn`. */\n readonly logger?: (message: string, meta?: object) => void;\n}\n\nconst WARNED = new Set<string>();\n\n/**\n * Counter that estimates tokens via simple character division. The\n * estimator is deterministic and side-effect-free apart from the\n * one-time WARN.\n *\n * @stable\n */\nexport class HeuristicCounter implements TokenCounter {\n readonly id: string;\n readonly version: string;\n readonly #charsPerToken: number;\n readonly #modelId?: string;\n readonly #logger: (message: string, meta?: object) => void;\n #warnedOnce = false;\n\n constructor(options: HeuristicCounterOptions = {}) {\n const charsPerToken = options.charsPerToken ?? 4;\n if (!(charsPerToken > 0)) {\n throw new RangeError('HeuristicCounter: charsPerToken must be > 0.');\n }\n this.#charsPerToken = charsPerToken;\n this.id = options.id ?? `heuristic@${charsPerToken}cpt`;\n this.version = `heuristic-${charsPerToken}-cpt-v1`;\n if (options.modelId !== undefined) this.#modelId = options.modelId;\n this.#logger = options.logger ?? defaultLogger;\n }\n\n async count(messages: ReadonlyArray<Message>): Promise<number> {\n this.#warnIfNeeded();\n let total = 0;\n for (const msg of messages) {\n const serialised = serialiseMessageForCount(msg);\n total += this.#estimate(serializedToString(serialised));\n }\n return total;\n }\n\n async countText(text: string): Promise<number> {\n this.#warnIfNeeded();\n return this.#estimate(text);\n }\n\n #estimate(text: string): number {\n if (text.length === 0) return 0;\n return Math.ceil(text.length / this.#charsPerToken);\n }\n\n #warnIfNeeded(): void {\n if (this.#warnedOnce) return;\n const key = `${this.id}::${this.#modelId ?? 'unknown'}`;\n if (WARNED.has(key)) {\n this.#warnedOnce = true;\n return;\n }\n WARNED.add(key);\n this.#warnedOnce = true;\n this.#logger(\n `[graphorin/provider] heuristic token counter active (${this.id}); accuracy is approximate (~chars/${this.#charsPerToken}).`,\n this.#modelId !== undefined ? { modelId: this.#modelId } : {},\n );\n }\n}\n\nfunction defaultLogger(message: string, meta?: object): void {\n if (meta !== undefined) console.warn(message, meta);\n else console.warn(message);\n}\n\n/**\n * Test-only hook that resets the per-process WARN cache.\n *\n * @internal\n */\nexport function __resetHeuristicWarnCache(): void {\n WARNED.clear();\n}\n"],"mappings":";;;AA6BA,MAAM,yBAAS,IAAI,KAAa;;;;;;;;AAShC,IAAa,mBAAb,MAAsD;CACpD,AAAS;CACT,AAAS;CACT,CAASA;CACT,CAASC;CACT,CAASC;CACT,cAAc;CAEd,YAAY,UAAmC,EAAE,EAAE;EACjD,MAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,MAAI,EAAE,gBAAgB,GACpB,OAAM,IAAI,WAAW,+CAA+C;AAEtE,QAAKF,gBAAiB;AACtB,OAAK,KAAK,QAAQ,MAAM,aAAa,cAAc;AACnD,OAAK,UAAU,aAAa,cAAc;AAC1C,MAAI,QAAQ,YAAY,OAAW,OAAKC,UAAW,QAAQ;AAC3D,QAAKC,SAAU,QAAQ,UAAU;;CAGnC,MAAM,MAAM,UAAmD;AAC7D,QAAKC,cAAe;EACpB,IAAI,QAAQ;AACZ,OAAK,MAAM,OAAO,UAAU;GAC1B,MAAM,aAAa,yBAAyB,IAAI;AAChD,YAAS,MAAKC,SAAU,mBAAmB,WAAW,CAAC;;AAEzD,SAAO;;CAGT,MAAM,UAAU,MAA+B;AAC7C,QAAKD,cAAe;AACpB,SAAO,MAAKC,SAAU,KAAK;;CAG7B,UAAU,MAAsB;AAC9B,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,SAAO,KAAK,KAAK,KAAK,SAAS,MAAKJ,cAAe;;CAGrD,gBAAsB;AACpB,MAAI,MAAKK,WAAa;EACtB,MAAM,MAAM,GAAG,KAAK,GAAG,IAAI,MAAKJ,WAAY;AAC5C,MAAI,OAAO,IAAI,IAAI,EAAE;AACnB,SAAKI,aAAc;AACnB;;AAEF,SAAO,IAAI,IAAI;AACf,QAAKA,aAAc;AACnB,QAAKH,OACH,wDAAwD,KAAK,GAAG,qCAAqC,MAAKF,cAAe,KACzH,MAAKC,YAAa,SAAY,EAAE,SAAS,MAAKA,SAAU,GAAG,EAAE,CAC9D;;;AAIL,SAAS,cAAc,SAAiB,MAAqB;AAC3D,KAAI,SAAS,OAAW,SAAQ,KAAK,SAAS,KAAK;KAC9C,SAAQ,KAAK,QAAQ"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AnthropicAPICounter, AnthropicAPICounterOptions } from "./anthropic.js";
|
|
2
|
+
import { BedrockAPICounter, BedrockAPICounterOptions } from "./bedrock.js";
|
|
3
|
+
import { CreateDefaultCounterOptions, createDefaultCounter } from "./dispatcher.js";
|
|
4
|
+
import { __resetGlobalTokenCounter, getGlobalTokenCounter, setGlobalTokenCounter } from "./global.js";
|
|
5
|
+
import { GoogleAPICounter, GoogleAPICounterOptions } from "./google.js";
|
|
6
|
+
import { HeuristicCounter, HeuristicCounterOptions } from "./heuristic.js";
|
|
7
|
+
import { JsTiktokenCounter, JsTiktokenCounterOptions, __resetTiktokenCache } from "./js-tiktoken.js";
|
|
8
|
+
import { SerializedMessage, serialiseMessageForCount, serializedToString } from "./serialize.js";
|
|
9
|
+
export { AnthropicAPICounter, type AnthropicAPICounterOptions, BedrockAPICounter, type BedrockAPICounterOptions, type CreateDefaultCounterOptions, GoogleAPICounter, type GoogleAPICounterOptions, HeuristicCounter, type HeuristicCounterOptions, JsTiktokenCounter, type JsTiktokenCounterOptions, type SerializedMessage, __resetGlobalTokenCounter, __resetTiktokenCache, createDefaultCounter, getGlobalTokenCounter, serialiseMessageForCount, serializedToString, setGlobalTokenCounter };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { serialiseMessageForCount, serializedToString } from "./serialize.js";
|
|
2
|
+
import { JsTiktokenCounter, __resetTiktokenCache } from "./js-tiktoken.js";
|
|
3
|
+
import { AnthropicAPICounter } from "./anthropic.js";
|
|
4
|
+
import { BedrockAPICounter } from "./bedrock.js";
|
|
5
|
+
import { GoogleAPICounter } from "./google.js";
|
|
6
|
+
import { HeuristicCounter } from "./heuristic.js";
|
|
7
|
+
import { createDefaultCounter } from "./dispatcher.js";
|
|
8
|
+
import { __resetGlobalTokenCounter, getGlobalTokenCounter, setGlobalTokenCounter } from "./global.js";
|
|
9
|
+
|
|
10
|
+
export { AnthropicAPICounter, BedrockAPICounter, GoogleAPICounter, HeuristicCounter, JsTiktokenCounter, __resetGlobalTokenCounter, __resetTiktokenCache, createDefaultCounter, getGlobalTokenCounter, serialiseMessageForCount, serializedToString, setGlobalTokenCounter };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Message, TokenCounter } from "@graphorin/core";
|
|
2
|
+
|
|
3
|
+
//#region src/counters/js-tiktoken.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Loose shape of a `js-tiktoken` encoding. Matches the public
|
|
7
|
+
* `Tiktoken` instance returned by `getEncoding(...)` in the upstream
|
|
8
|
+
* package (we only use `encode` / `name`).
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
interface TiktokenEncoding {
|
|
13
|
+
readonly name?: string;
|
|
14
|
+
encode(text: string): {
|
|
15
|
+
length: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
interface TiktokenModule {
|
|
19
|
+
getEncoding(name: string): TiktokenEncoding;
|
|
20
|
+
encodingForModel?: (model: string) => TiktokenEncoding;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Options for {@link JsTiktokenCounter}.
|
|
24
|
+
*
|
|
25
|
+
* @stable
|
|
26
|
+
*/
|
|
27
|
+
interface JsTiktokenCounterOptions {
|
|
28
|
+
/** Encoding name (`'cl100k_base'`, `'o200k_base'`, …). Default `'cl100k_base'`. */
|
|
29
|
+
readonly encoding?: string;
|
|
30
|
+
/** Optional model id used by `encodingForModel`. Falls back to the explicit `encoding`. */
|
|
31
|
+
readonly modelId?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Override the dynamically-loaded module. Used by tests to inject a
|
|
34
|
+
* stub without taking the real `js-tiktoken` peer dependency.
|
|
35
|
+
*/
|
|
36
|
+
readonly moduleOverride?: TiktokenModule;
|
|
37
|
+
/** Identifier carried on the produced counter. */
|
|
38
|
+
readonly id?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Counter that delegates to the `js-tiktoken` package. Caches the
|
|
42
|
+
* dynamically-loaded module per process; tests use `moduleOverride`
|
|
43
|
+
* to supply a fixture-shaped substitute.
|
|
44
|
+
*
|
|
45
|
+
* @stable
|
|
46
|
+
*/
|
|
47
|
+
declare class JsTiktokenCounter implements TokenCounter {
|
|
48
|
+
#private;
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly version: string;
|
|
51
|
+
constructor(options?: JsTiktokenCounterOptions);
|
|
52
|
+
count(messages: ReadonlyArray<Message>): Promise<number>;
|
|
53
|
+
countText(text: string): Promise<number>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Test-only hook that resets the cached `js-tiktoken` module loader.
|
|
57
|
+
*
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
declare function __resetTiktokenCache(): void;
|
|
61
|
+
//#endregion
|
|
62
|
+
export { JsTiktokenCounter, JsTiktokenCounterOptions, __resetTiktokenCache };
|
|
63
|
+
//# sourceMappingURL=js-tiktoken.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"js-tiktoken.d.ts","names":[],"sources":["../../src/counters/js-tiktoken.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAoIA;;UAhHU,gBAAA;;;;;;UAKA,cAAA;6BACmB;wCACW;;;;;;;UAQvB,wBAAA;;;;;;;;;4BASW;;;;;;;;;;;cAcf,iBAAA,YAA6B;;;;wBAQnB;kBAQC,cAAc,WAAW;2BAUhB;;;;;;;iBAgDjB,oBAAA,CAAA"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { serialiseMessageForCount, serializedToString } from "./serialize.js";
|
|
2
|
+
|
|
3
|
+
//#region src/counters/js-tiktoken.ts
|
|
4
|
+
let cachedModule = null;
|
|
5
|
+
/**
|
|
6
|
+
* Counter that delegates to the `js-tiktoken` package. Caches the
|
|
7
|
+
* dynamically-loaded module per process; tests use `moduleOverride`
|
|
8
|
+
* to supply a fixture-shaped substitute.
|
|
9
|
+
*
|
|
10
|
+
* @stable
|
|
11
|
+
*/
|
|
12
|
+
var JsTiktokenCounter = class {
|
|
13
|
+
id;
|
|
14
|
+
version;
|
|
15
|
+
#encoding;
|
|
16
|
+
#modelId;
|
|
17
|
+
#moduleOverride;
|
|
18
|
+
#encoder = null;
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.#encoding = options.encoding ?? "cl100k_base";
|
|
21
|
+
if (options.modelId !== void 0) this.#modelId = options.modelId;
|
|
22
|
+
if (options.moduleOverride !== void 0) this.#moduleOverride = options.moduleOverride;
|
|
23
|
+
this.id = options.id ?? `js-tiktoken@${this.#encoding}`;
|
|
24
|
+
this.version = `js-tiktoken-${this.#encoding}-v1`;
|
|
25
|
+
}
|
|
26
|
+
async count(messages) {
|
|
27
|
+
const enc = await this.#getEncoder();
|
|
28
|
+
let total = 0;
|
|
29
|
+
for (const msg of messages) {
|
|
30
|
+
const serialised = serialiseMessageForCount(msg);
|
|
31
|
+
total += enc.encode(serializedToString(serialised)).length;
|
|
32
|
+
}
|
|
33
|
+
return total;
|
|
34
|
+
}
|
|
35
|
+
async countText(text) {
|
|
36
|
+
if (text.length === 0) return 0;
|
|
37
|
+
return (await this.#getEncoder()).encode(text).length;
|
|
38
|
+
}
|
|
39
|
+
async #getEncoder() {
|
|
40
|
+
if (this.#encoder !== null) return this.#encoder;
|
|
41
|
+
const mod = this.#moduleOverride ?? await loadTiktokenModule();
|
|
42
|
+
if (this.#modelId !== void 0 && mod.encodingForModel !== void 0) try {
|
|
43
|
+
this.#encoder = mod.encodingForModel(this.#modelId);
|
|
44
|
+
return this.#encoder;
|
|
45
|
+
} catch {}
|
|
46
|
+
this.#encoder = mod.getEncoding(this.#encoding);
|
|
47
|
+
return this.#encoder;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
async function loadTiktokenModule() {
|
|
51
|
+
if (cachedModule !== null) return cachedModule;
|
|
52
|
+
let mod;
|
|
53
|
+
try {
|
|
54
|
+
mod = await import("js-tiktoken");
|
|
55
|
+
} catch (cause) {
|
|
56
|
+
throw new Error("[graphorin/provider] JsTiktokenCounter requires the 'js-tiktoken' peer dependency. Install it with `pnpm add js-tiktoken` or pass moduleOverride for tests.", { cause });
|
|
57
|
+
}
|
|
58
|
+
if (typeof mod.getEncoding !== "function") throw new Error("[graphorin/provider] JsTiktokenCounter: installed js-tiktoken does not expose getEncoding().");
|
|
59
|
+
cachedModule = mod;
|
|
60
|
+
return cachedModule;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Test-only hook that resets the cached `js-tiktoken` module loader.
|
|
64
|
+
*
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
function __resetTiktokenCache() {
|
|
68
|
+
cachedModule = null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
export { JsTiktokenCounter, __resetTiktokenCache };
|
|
73
|
+
//# sourceMappingURL=js-tiktoken.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"js-tiktoken.js","names":["cachedModule: TiktokenModule | null","#encoding","#modelId","#moduleOverride","#getEncoder","#encoder","mod: TiktokenModule"],"sources":["../../src/counters/js-tiktoken.ts"],"sourcesContent":["/**\n * `JsTiktokenCounter` — default counter for OpenAI / OpenAI-compatible\n * models. The package depends on the `js-tiktoken` peer; when the peer\n * is missing, instantiation throws at first call rather than silently\n * downgrading to a heuristic.\n *\n * @packageDocumentation\n */\n\nimport type { Message, TokenCounter } from '@graphorin/core';\n\nimport { serialiseMessageForCount, serializedToString } from './serialize.js';\n\n/**\n * Loose shape of a `js-tiktoken` encoding. Matches the public\n * `Tiktoken` instance returned by `getEncoding(...)` in the upstream\n * package (we only use `encode` / `name`).\n *\n * @internal\n */\ninterface TiktokenEncoding {\n readonly name?: string;\n encode(text: string): { length: number };\n}\n\ninterface TiktokenModule {\n getEncoding(name: string): TiktokenEncoding;\n encodingForModel?: (model: string) => TiktokenEncoding;\n}\n\n/**\n * Options for {@link JsTiktokenCounter}.\n *\n * @stable\n */\nexport interface JsTiktokenCounterOptions {\n /** Encoding name (`'cl100k_base'`, `'o200k_base'`, …). Default `'cl100k_base'`. */\n readonly encoding?: string;\n /** Optional model id used by `encodingForModel`. Falls back to the explicit `encoding`. */\n readonly modelId?: string;\n /**\n * Override the dynamically-loaded module. Used by tests to inject a\n * stub without taking the real `js-tiktoken` peer dependency.\n */\n readonly moduleOverride?: TiktokenModule;\n /** Identifier carried on the produced counter. */\n readonly id?: string;\n}\n\nlet cachedModule: TiktokenModule | null = null;\n\n/**\n * Counter that delegates to the `js-tiktoken` package. Caches the\n * dynamically-loaded module per process; tests use `moduleOverride`\n * to supply a fixture-shaped substitute.\n *\n * @stable\n */\nexport class JsTiktokenCounter implements TokenCounter {\n readonly id: string;\n readonly version: string;\n readonly #encoding: string;\n readonly #modelId?: string;\n readonly #moduleOverride?: TiktokenModule;\n #encoder: TiktokenEncoding | null = null;\n\n constructor(options: JsTiktokenCounterOptions = {}) {\n this.#encoding = options.encoding ?? 'cl100k_base';\n if (options.modelId !== undefined) this.#modelId = options.modelId;\n if (options.moduleOverride !== undefined) this.#moduleOverride = options.moduleOverride;\n this.id = options.id ?? `js-tiktoken@${this.#encoding}`;\n this.version = `js-tiktoken-${this.#encoding}-v1`;\n }\n\n async count(messages: ReadonlyArray<Message>): Promise<number> {\n const enc = await this.#getEncoder();\n let total = 0;\n for (const msg of messages) {\n const serialised = serialiseMessageForCount(msg);\n total += enc.encode(serializedToString(serialised)).length;\n }\n return total;\n }\n\n async countText(text: string): Promise<number> {\n if (text.length === 0) return 0;\n const enc = await this.#getEncoder();\n return enc.encode(text).length;\n }\n\n async #getEncoder(): Promise<TiktokenEncoding> {\n if (this.#encoder !== null) return this.#encoder;\n const mod = this.#moduleOverride ?? (await loadTiktokenModule());\n if (this.#modelId !== undefined && mod.encodingForModel !== undefined) {\n try {\n this.#encoder = mod.encodingForModel(this.#modelId);\n return this.#encoder;\n } catch {\n // Fall through to the explicit encoding.\n }\n }\n this.#encoder = mod.getEncoding(this.#encoding);\n return this.#encoder;\n }\n}\n\nasync function loadTiktokenModule(): Promise<TiktokenModule> {\n if (cachedModule !== null) return cachedModule;\n let mod: TiktokenModule;\n try {\n mod = (await import('js-tiktoken')) as TiktokenModule;\n } catch (cause) {\n throw new Error(\n \"[graphorin/provider] JsTiktokenCounter requires the 'js-tiktoken' peer dependency. \" +\n 'Install it with `pnpm add js-tiktoken` or pass moduleOverride for tests.',\n { cause },\n );\n }\n if (typeof mod.getEncoding !== 'function') {\n throw new Error(\n '[graphorin/provider] JsTiktokenCounter: installed js-tiktoken does not expose getEncoding().',\n );\n }\n cachedModule = mod;\n return cachedModule;\n}\n\n/**\n * Test-only hook that resets the cached `js-tiktoken` module loader.\n *\n * @internal\n */\nexport function __resetTiktokenCache(): void {\n cachedModule = null;\n}\n"],"mappings":";;;AAiDA,IAAIA,eAAsC;;;;;;;;AAS1C,IAAa,oBAAb,MAAuD;CACrD,AAAS;CACT,AAAS;CACT,CAASC;CACT,CAASC;CACT,CAASC;CACT,WAAoC;CAEpC,YAAY,UAAoC,EAAE,EAAE;AAClD,QAAKF,WAAY,QAAQ,YAAY;AACrC,MAAI,QAAQ,YAAY,OAAW,OAAKC,UAAW,QAAQ;AAC3D,MAAI,QAAQ,mBAAmB,OAAW,OAAKC,iBAAkB,QAAQ;AACzE,OAAK,KAAK,QAAQ,MAAM,eAAe,MAAKF;AAC5C,OAAK,UAAU,eAAe,MAAKA,SAAU;;CAG/C,MAAM,MAAM,UAAmD;EAC7D,MAAM,MAAM,MAAM,MAAKG,YAAa;EACpC,IAAI,QAAQ;AACZ,OAAK,MAAM,OAAO,UAAU;GAC1B,MAAM,aAAa,yBAAyB,IAAI;AAChD,YAAS,IAAI,OAAO,mBAAmB,WAAW,CAAC,CAAC;;AAEtD,SAAO;;CAGT,MAAM,UAAU,MAA+B;AAC7C,MAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,UADY,MAAM,MAAKA,YAAa,EACzB,OAAO,KAAK,CAAC;;CAG1B,OAAMA,aAAyC;AAC7C,MAAI,MAAKC,YAAa,KAAM,QAAO,MAAKA;EACxC,MAAM,MAAM,MAAKF,kBAAoB,MAAM,oBAAoB;AAC/D,MAAI,MAAKD,YAAa,UAAa,IAAI,qBAAqB,OAC1D,KAAI;AACF,SAAKG,UAAW,IAAI,iBAAiB,MAAKH,QAAS;AACnD,UAAO,MAAKG;UACN;AAIV,QAAKA,UAAW,IAAI,YAAY,MAAKJ,SAAU;AAC/C,SAAO,MAAKI;;;AAIhB,eAAe,qBAA8C;AAC3D,KAAI,iBAAiB,KAAM,QAAO;CAClC,IAAIC;AACJ,KAAI;AACF,QAAO,MAAM,OAAO;UACb,OAAO;AACd,QAAM,IAAI,MACR,+JAEA,EAAE,OAAO,CACV;;AAEH,KAAI,OAAO,IAAI,gBAAgB,WAC7B,OAAM,IAAI,MACR,+FACD;AAEH,gBAAe;AACf,QAAO;;;;;;;AAQT,SAAgB,uBAA6B;AAC3C,gBAAe"}
|