dsh-context-compression-improved 0.1.1 → 0.2.1
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/.gitattributes +1 -0
- package/.github/workflows/ci.yml +39 -0
- package/CHANGELOG.ja.md +39 -0
- package/CHANGELOG.ko.md +39 -0
- package/CHANGELOG.md +135 -0
- package/CHANGELOG.zh.md +39 -0
- package/CONTRIBUTING.md +22 -0
- package/README.ja.md +104 -0
- package/README.ko.md +103 -0
- package/README.md +89 -12
- package/README.zh.md +87 -12
- package/SECURITY.md +18 -0
- package/THIRD_PARTY_NOTICES.md +7 -31
- package/docs/installation.ja.md +76 -0
- package/docs/installation.ko.md +76 -0
- package/docs/installation.md +76 -0
- package/docs/installation.zh.md +76 -0
- package/docs/repair-log.md +582 -0
- package/eslint.config.js +30 -0
- package/package.json +85 -82
- package/packages/selector/LICENSE +21 -0
- package/packages/selector/README.md +26 -0
- package/packages/selector/README.zh.md +26 -0
- package/packages/selector/THIRD_PARTY_NOTICES.md +38 -0
- package/packages/selector/docs/history-tool-call-working-set-spec.md +112 -0
- package/packages/selector/docs/native-tool-result-selector-spec.md +34 -0
- package/packages/selector/docs/subagent-cache-reuse-spec.md +46 -0
- package/packages/selector/lib/style.css +308 -0
- package/packages/selector/package.json +115 -0
- package/{screenshots.json → packages/selector/screenshots.json} +6 -6
- package/packages/selector/src/client/CompressionProfileControls.tsx +229 -0
- package/packages/selector/src/client/CompressionProfileSelector.module.css +170 -0
- package/packages/selector/src/client/CompressionProfileSelector.tsx +79 -0
- package/packages/selector/src/client/CustomPolicyEditor.tsx +216 -0
- package/packages/selector/src/client/EstimatorControls.tsx +281 -0
- package/packages/selector/src/client/decode.ts +49 -0
- package/packages/selector/src/client/index.ts +111 -0
- package/packages/selector/src/client/locales.ts +198 -0
- package/packages/selector/src/client/preset-options.ts +70 -0
- package/packages/selector/src/client/settings-section.tsx +126 -0
- package/packages/selector/src/css-modules.d.ts +6 -0
- package/packages/selector/src/deepseek-v4-tokenizer.ts +210 -0
- package/packages/selector/src/estimator-catalog.ts +104 -0
- package/packages/selector/src/index.ts +327 -0
- package/packages/selector/src/invariant.ts +113 -0
- package/packages/selector/src/preset-overlay.ts +567 -0
- package/packages/selector/src/profiles.ts +342 -0
- package/packages/selector/src/pruner/content.ts +188 -0
- package/packages/selector/src/pruner/session.ts +94 -0
- package/packages/selector/src/pruner/state.ts +43 -0
- package/packages/selector/src/pruner/tuning.ts +23 -0
- package/packages/selector/src/pruner/types.ts +60 -0
- package/packages/selector/src/pruner.ts +2144 -0
- package/packages/selector/src/runtime/adaptive-cost.ts +194 -0
- package/packages/selector/src/runtime/audit.ts +215 -0
- package/packages/selector/src/runtime/config.ts +613 -0
- package/packages/selector/src/runtime/custom-policy.ts +278 -0
- package/packages/selector/src/runtime/deepseek-official-pricing.ts +298 -0
- package/packages/selector/src/runtime/deepseek-v4-vision-tokens.ts +254 -0
- package/packages/selector/src/runtime/measurement.ts +403 -0
- package/packages/selector/src/runtime/reducers.ts +656 -0
- package/packages/selector/src/runtime/retrieve.ts +457 -0
- package/packages/selector/src/runtime/session-events.ts +17 -0
- package/packages/selector/src/runtime/tail-trim.ts +166 -0
- package/packages/selector/src/runtime/token-count.ts +72 -0
- package/packages/selector/src/runtime/tokenpilot/dedup.ts +81 -0
- package/packages/selector/src/runtime/tokenpilot/estimator.ts +183 -0
- package/packages/selector/src/runtime/tokenpilot/locator.ts +128 -0
- package/packages/selector/src/runtime/tokenpilot/read-state.ts +77 -0
- package/packages/selector/src/runtime/types.ts +309 -0
- package/packages/selector/src/runtime/value.ts +48 -0
- package/packages/selector/tests/auto-compact.client.spec.tsx +226 -0
- package/packages/selector/tests/built/client-artifact.spec.ts +51 -0
- package/packages/selector/tests/cache-prefix-audit.spec.ts +123 -0
- package/packages/selector/tests/code-skeleton.client.spec.ts +88 -0
- package/packages/selector/tests/custom-contract.client.spec.ts +202 -0
- package/packages/selector/tests/estimator-catalog.spec.ts +70 -0
- package/packages/selector/tests/estimator-channel.client.spec.tsx +247 -0
- package/packages/selector/tests/estimator-route-registration.host.spec.ts +176 -0
- package/packages/selector/tests/host-preset-overlay.host.spec.ts +204 -0
- package/packages/selector/tests/preset-options-write.client.spec.ts +181 -0
- package/packages/selector/tests/preset-overlay-loader.e2e.host.spec.ts +196 -0
- package/packages/selector/tests/preset-overlay.host.spec.ts +243 -0
- package/packages/selector/tests/profiles.client.spec.tsx +434 -0
- package/packages/selector/tests/public/package-contract.client.spec.ts +33 -0
- package/packages/selector/tests/runtime/adaptive-cost.spec.ts +167 -0
- package/packages/selector/tests/runtime/audit.spec.ts +129 -0
- package/packages/selector/tests/runtime/auto-compact-config.spec.ts +523 -0
- package/packages/selector/tests/runtime/code-skeleton.spec.ts +141 -0
- package/packages/selector/tests/runtime/deepseek-official-pricing.spec.ts +186 -0
- package/packages/selector/tests/runtime/deepseek-v4-tokenizer.spec.ts +122 -0
- package/packages/selector/tests/runtime/deepseek-v4-vision-tokens.spec.ts +122 -0
- package/packages/selector/tests/runtime/fixtures/profile-baseline.json +273 -0
- package/packages/selector/tests/runtime/fixtures/tokenizer-golden.json +106 -0
- package/packages/selector/tests/runtime/fixtures/vision-golden.json +459 -0
- package/packages/selector/tests/runtime/public/public-runtime.spec.ts +2531 -0
- package/packages/selector/tests/runtime/session-events.spec.ts +27 -0
- package/packages/selector/tests/runtime/tokenizer-golden.spec.ts +53 -0
- package/packages/selector/tests/runtime/tokenpilot/dedup.spec.ts +52 -0
- package/packages/selector/tests/runtime/tokenpilot/estimator.spec.ts +56 -0
- package/packages/selector/tests/runtime/tokenpilot/locator.spec.ts +76 -0
- package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +100 -0
- package/packages/selector/tests/runtime/tokenpilot/read-state.spec.ts +58 -0
- package/packages/selector/tests/runtime/value.spec.ts +23 -0
- package/packages/selector/tests/standing-generation.host.spec.ts +631 -0
- package/packages/selector/tests/subagent-cache-reuse.host.spec.ts +250 -0
- package/packages/selector/tests/support/cache-prefix-audit.ts +105 -0
- package/packages/selector/tests/support/mock-adapter.ts +37 -0
- package/packages/selector/tests/support/ui-primitives.tsx +34 -0
- package/packages/selector/tsconfig.json +11 -0
- package/packages/selector/tsdown.client.config.ts +102 -0
- package/packages/selector/tsdown.config.ts +20 -0
- package/pnpm-workspace.yaml +19 -0
- package/scripts/capture-profile-baseline.ts +80 -0
- package/scripts/generate-tokenizer-fixtures.py +81 -0
- package/scripts/generate-vision-fixtures.py +208 -0
- package/scripts/packed-components-smoke.ts +713 -0
- package/scripts/packed-install-e2e.ts +1072 -0
- package/scripts/verify-release.ts +300 -0
- package/tests/TEST_INVENTORY.md +42 -0
- package/tsconfig.base.json +18 -0
- package/tsconfig.json +7 -0
- package/tsconfig.scripts.json +13 -0
- package/tsconfig.tests.json +15 -0
- package/vitest.built.config.ts +9 -0
- package/vitest.config.ts +43 -0
- /package/{assets → packages/selector/assets}/deepseek-v4/LICENSE.DeepSeek-V4-Pro.txt +0 -0
- /package/{assets → packages/selector/assets}/deepseek-v4/manifest.json +0 -0
- /package/{assets → packages/selector/assets}/deepseek-v4/tokenizer.json +0 -0
- /package/{assets → packages/selector/assets}/deepseek-v4/tokenizer_config.json +0 -0
- /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/LICENSE.DeepSeek-V4-Flash-Vision-Exp.txt +0 -0
- /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/manifest.json +0 -0
- /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/tokenizer.json +0 -0
- /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/tokenizer_config.json +0 -0
- /package/{assets → packages/selector/assets}/screenshots/context-compression-selector-profiles.jpg +0 -0
- /package/{assets → packages/selector/assets}/screenshots/context-compression-selector-settings.png +0 -0
- /package/{cordis.patch.yml → packages/selector/cordis.patch.yml} +0 -0
- /package/{dsh.plugin.json → packages/selector/dsh.plugin.json} +0 -0
- /package/{lib → packages/selector/lib}/client.d.ts +0 -0
- /package/{lib → packages/selector/lib}/client.js +0 -0
- /package/{lib → packages/selector/lib}/config.js +0 -0
- /package/{lib → packages/selector/lib}/index.d.ts +0 -0
- /package/{lib → packages/selector/lib}/index.js +0 -0
- /package/{lib → packages/selector/lib}/invariant.d.ts +0 -0
- /package/{lib → packages/selector/lib}/invariant.js +0 -0
- /package/{lib → packages/selector/lib}/pruner.d.ts +0 -0
- /package/{lib → packages/selector/lib}/pruner.js +0 -0
- /package/{lib → packages/selector/lib}/tail-trim.js +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { codePointLength } from '../../src/runtime/config.ts'
|
|
3
|
+
import { reduceFreshToolResult, verifyReduction } from '../../src/runtime/reducers.ts'
|
|
4
|
+
|
|
5
|
+
const SOURCE_REF = 'session://s1/event/12'
|
|
6
|
+
|
|
7
|
+
function readInput(
|
|
8
|
+
text: string,
|
|
9
|
+
budgetChars = 700,
|
|
10
|
+
codeSkeleton?: boolean,
|
|
11
|
+
): Parameters<typeof reduceFreshToolResult>[0] {
|
|
12
|
+
return {
|
|
13
|
+
toolName: 'read_file',
|
|
14
|
+
argumentsText: '{"path":"src/example.ts"}',
|
|
15
|
+
text,
|
|
16
|
+
budgetChars,
|
|
17
|
+
sourceRef: SOURCE_REF,
|
|
18
|
+
isError: false,
|
|
19
|
+
...codeSkeleton === undefined ? {} : { codeSkeleton },
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const TYPESCRIPT_FILE = [
|
|
24
|
+
'/** Module doc block. */',
|
|
25
|
+
"import { helper } from './helper'",
|
|
26
|
+
'import type { Options } from "./options"',
|
|
27
|
+
'',
|
|
28
|
+
'export interface Options {',
|
|
29
|
+
' retries: number',
|
|
30
|
+
' timeout: number',
|
|
31
|
+
'}',
|
|
32
|
+
'',
|
|
33
|
+
'export function runJob(job: string,',
|
|
34
|
+
' retries: number,',
|
|
35
|
+
' timeout: number) {',
|
|
36
|
+
' const started = Date.now()',
|
|
37
|
+
" throw new Error('fatal: job queue unreachable')",
|
|
38
|
+
' return started',
|
|
39
|
+
'}',
|
|
40
|
+
'',
|
|
41
|
+
'class Runner {',
|
|
42
|
+
' private stopped = false',
|
|
43
|
+
' stop(): void {',
|
|
44
|
+
' this.stopped = true',
|
|
45
|
+
' }',
|
|
46
|
+
'}',
|
|
47
|
+
...Array.from({ length: 40 }, (_, i) => `// filler line ${String(i)} to make the file worth compressing`),
|
|
48
|
+
].join('\n')
|
|
49
|
+
|
|
50
|
+
const PYTHON_FILE = [
|
|
51
|
+
'"""Docstring."""',
|
|
52
|
+
'import os',
|
|
53
|
+
'from pathlib import Path',
|
|
54
|
+
'',
|
|
55
|
+
'class Runner:',
|
|
56
|
+
' def __init__(self, name,',
|
|
57
|
+
' retries=3):',
|
|
58
|
+
' self.name = name',
|
|
59
|
+
' raise RuntimeError("fatal: bad config")',
|
|
60
|
+
'',
|
|
61
|
+
' def run(self):',
|
|
62
|
+
' return os.getcwd()',
|
|
63
|
+
...Array.from({ length: 40 }, (_, i) => `# filler line ${String(i)} to make the file worth compressing`),
|
|
64
|
+
].join('\n')
|
|
65
|
+
|
|
66
|
+
describe('hypa-code-skeleton reducer', () => {
|
|
67
|
+
it('keeps imports and signatures while eliding brace-language bodies', () => {
|
|
68
|
+
const input = readInput(TYPESCRIPT_FILE, 700, true)
|
|
69
|
+
const output = reduceFreshToolResult(input)
|
|
70
|
+
expect(output).not.toBeNull()
|
|
71
|
+
expect(output?.reducer).toBe('hypa-code-skeleton')
|
|
72
|
+
expect(output?.text).toContain("import { helper } from './helper'")
|
|
73
|
+
expect(output?.text).toContain('export interface Options {')
|
|
74
|
+
expect(output?.text).toContain('export function runJob(job: string,')
|
|
75
|
+
expect(output?.text).toContain('class Runner {')
|
|
76
|
+
expect(output?.text).toContain('lines elided')
|
|
77
|
+
expect(output?.text).not.toContain('const started = Date.now()')
|
|
78
|
+
expect(output?.text).toContain(SOURCE_REF)
|
|
79
|
+
expect(verifyReduction(input, output!)).toBe(true)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('preserves error lines from inside elided bodies', () => {
|
|
83
|
+
const input = readInput(TYPESCRIPT_FILE, 700, true)
|
|
84
|
+
const output = reduceFreshToolResult(input)
|
|
85
|
+
expect(output?.text).toContain("throw new Error('fatal: job queue unreachable')")
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('keeps multi-line signature continuations', () => {
|
|
89
|
+
const input = readInput(TYPESCRIPT_FILE, 700, true)
|
|
90
|
+
const output = reduceFreshToolResult(input)
|
|
91
|
+
expect(output?.text).toContain(' retries: number,')
|
|
92
|
+
expect(output?.text).toContain(' timeout: number) {')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('elides indented Python bodies and keeps method signatures', () => {
|
|
96
|
+
const input = readInput(PYTHON_FILE, 700, true)
|
|
97
|
+
const output = reduceFreshToolResult(input)
|
|
98
|
+
expect(output).not.toBeNull()
|
|
99
|
+
expect(output?.reducer).toBe('hypa-code-skeleton')
|
|
100
|
+
expect(output?.text).toContain('class Runner:')
|
|
101
|
+
expect(output?.text).toContain('def __init__(self, name,')
|
|
102
|
+
expect(output?.text).toContain('def run(self):')
|
|
103
|
+
expect(output?.text).not.toContain('return os.getcwd()')
|
|
104
|
+
expect(output?.text).toContain('raise RuntimeError("fatal: bad config")')
|
|
105
|
+
expect(verifyReduction(input, output!)).toBe(true)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('stays within the char budget', () => {
|
|
109
|
+
const input = readInput(TYPESCRIPT_FILE, 1_200, true)
|
|
110
|
+
const output = reduceFreshToolResult(input)
|
|
111
|
+
expect(output?.reducer).toBe('hypa-code-skeleton')
|
|
112
|
+
expect(codePointLength(output!.text)).toBeLessThanOrEqual(1_200)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('leaves prose on the existing head reducer', () => {
|
|
116
|
+
const prose = Array.from({ length: 200 }, (_, i) =>
|
|
117
|
+
`Paragraph ${String(i)} explains a concept in plain sentences with no code structure at all.`).join('\n')
|
|
118
|
+
const input = readInput(prose)
|
|
119
|
+
const output = reduceFreshToolResult(input)
|
|
120
|
+
expect(output?.reducer).toBe('pi-head')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('does not fire on small or sparse content', () => {
|
|
124
|
+
const sparse = ['function a() {}', 'function b() {}', 'plain text line'].join('\n')
|
|
125
|
+
expect(reduceFreshToolResult(readInput(sparse))?.reducer).not.toBe('hypa-code-skeleton')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('stays off for code content when the orthogonal gate is unset or false', () => {
|
|
129
|
+
for (const codeSkeleton of [undefined, false]) {
|
|
130
|
+
const input = readInput(TYPESCRIPT_FILE, 700, codeSkeleton)
|
|
131
|
+
const output = reduceFreshToolResult(input)
|
|
132
|
+
expect(output?.reducer).not.toBe('hypa-code-skeleton')
|
|
133
|
+
expect(output?.reducer).toBe('pi-head')
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('fires for code content only when the orthogonal gate is enabled', () => {
|
|
138
|
+
const enabled = reduceFreshToolResult(readInput(TYPESCRIPT_FILE, 700, true))
|
|
139
|
+
expect(enabled?.reducer).toBe('hypa-code-skeleton')
|
|
140
|
+
})
|
|
141
|
+
})
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
DEEPSEEK_OFFICIAL_PRICE_CATALOG_VERSION,
|
|
4
|
+
priceBandAt,
|
|
5
|
+
priceOfficialDeepSeekUsage,
|
|
6
|
+
resolveOfficialDeepSeekPrice,
|
|
7
|
+
} from '../../src/runtime/deepseek-official-pricing.ts'
|
|
8
|
+
|
|
9
|
+
describe('DeepSeek checked-in official price catalog', () => {
|
|
10
|
+
it.each([
|
|
11
|
+
['deepseek-v4-flash', 'USD', 'off-peak', '0.007', '0.22', '0.66'],
|
|
12
|
+
['deepseek-v4-flash', 'USD', 'peak', '0.014', '0.44', '1.32'],
|
|
13
|
+
['deepseek-v4-flash', 'CNY', 'off-peak', '0.05', '1.5', '4.5'],
|
|
14
|
+
['deepseek-v4-flash', 'CNY', 'peak', '0.10', '3.0', '9.0'],
|
|
15
|
+
['deepseek-v4-pro', 'USD', 'off-peak', '0.022', '0.66', '1.98'],
|
|
16
|
+
['deepseek-v4-pro', 'USD', 'peak', '0.044', '1.32', '3.96'],
|
|
17
|
+
['deepseek-v4-pro', 'CNY', 'off-peak', '0.15', '4.5', '13.5'],
|
|
18
|
+
['deepseek-v4-pro', 'CNY', 'peak', '0.30', '9.0', '27.0'],
|
|
19
|
+
['deepseek-v4-flash-vision-exp', 'USD', 'off-peak', '0.007', '0.22', '0.66'],
|
|
20
|
+
['deepseek-v4-flash-vision-exp', 'USD', 'peak', '0.014', '0.44', '1.32'],
|
|
21
|
+
['deepseek-v4-flash-vision-exp', 'CNY', 'off-peak', '0.05', '1.5', '4.5'],
|
|
22
|
+
['deepseek-v4-flash-vision-exp', 'CNY', 'peak', '0.10', '3.0', '9.0'],
|
|
23
|
+
] as const)(
|
|
24
|
+
'resolves %s %s %s from the versioned catalog',
|
|
25
|
+
(modelId, currency, band, hit, miss, output) => {
|
|
26
|
+
const resolved = resolveOfficialDeepSeekPrice({
|
|
27
|
+
provider: 'deepseek-official',
|
|
28
|
+
baseUrlClass: 'official-public',
|
|
29
|
+
apiRoute: 'chat-completions',
|
|
30
|
+
modelId,
|
|
31
|
+
currency,
|
|
32
|
+
at: band === 'peak'
|
|
33
|
+
? new Date('2026-08-25T01:00:00.000Z')
|
|
34
|
+
: new Date('2026-08-23T01:00:00.000Z'),
|
|
35
|
+
})
|
|
36
|
+
expect(resolved).toMatchObject({
|
|
37
|
+
kind: 'priced',
|
|
38
|
+
record: {
|
|
39
|
+
catalogVersion: DEEPSEEK_OFFICIAL_PRICE_CATALOG_VERSION,
|
|
40
|
+
modelId, currency, band,
|
|
41
|
+
inputCacheHit: hit,
|
|
42
|
+
inputCacheMiss: miss,
|
|
43
|
+
output,
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
it.each(['chat-completions', 'responses'] as const)(
|
|
50
|
+
'prices the documented %s route without changing the catalog tuple',
|
|
51
|
+
(apiRoute) => {
|
|
52
|
+
expect(resolveOfficialDeepSeekPrice({
|
|
53
|
+
provider: 'deepseek-official',
|
|
54
|
+
baseUrlClass: 'official-public',
|
|
55
|
+
apiRoute,
|
|
56
|
+
modelId: 'deepseek-v4-pro',
|
|
57
|
+
currency: 'USD',
|
|
58
|
+
at: new Date('2026-08-25T01:00:00.000Z'),
|
|
59
|
+
})).toMatchObject({
|
|
60
|
+
kind: 'priced',
|
|
61
|
+
record: { apiRoute, modelId: 'deepseek-v4-pro', band: 'peak' },
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
it.each([
|
|
67
|
+
['2026-08-24T00:59:59.000Z', 'off-peak'],
|
|
68
|
+
['2026-08-24T01:00:00.000Z', 'peak'],
|
|
69
|
+
['2026-08-24T03:59:59.000Z', 'peak'],
|
|
70
|
+
['2026-08-24T04:00:00.000Z', 'off-peak'],
|
|
71
|
+
['2026-08-24T05:59:59.000Z', 'off-peak'],
|
|
72
|
+
['2026-08-24T06:00:00.000Z', 'peak'],
|
|
73
|
+
['2026-08-24T09:59:59.000Z', 'peak'],
|
|
74
|
+
['2026-08-24T10:00:00.000Z', 'off-peak'],
|
|
75
|
+
['2026-08-29T01:00:00.000Z', 'off-peak'],
|
|
76
|
+
['2026-08-30T06:00:00.000Z', 'off-peak'],
|
|
77
|
+
] as const)('classifies exact UTC schedule boundary %s as %s', (iso, expected) => {
|
|
78
|
+
expect(priceBandAt(new Date(iso))).toBe(expected)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it.each([
|
|
82
|
+
[{ provider: 'gateway', baseUrlClass: 'official-public' }, 'provider'],
|
|
83
|
+
[{ provider: 'deepseek-official', baseUrlClass: 'compatible-hmac:v1:test' }, 'base-url'],
|
|
84
|
+
[{ provider: 'deepseek-official', baseUrlClass: 'official-public', modelId: 'deepseek-chat' }, 'model'],
|
|
85
|
+
[{ provider: 'deepseek-official', baseUrlClass: 'official-public', apiRoute: 'other' }, 'route'],
|
|
86
|
+
] as const)('fails closed instead of aliasing an unknown applicability: %s', (patch, reason) => {
|
|
87
|
+
const resolved = resolveOfficialDeepSeekPrice({
|
|
88
|
+
apiRoute: 'chat-completions',
|
|
89
|
+
modelId: 'deepseek-v4-flash',
|
|
90
|
+
currency: 'USD',
|
|
91
|
+
at: new Date('2026-08-25T01:00:00.000Z'),
|
|
92
|
+
...patch,
|
|
93
|
+
})
|
|
94
|
+
expect(resolved.kind).toBe('unpriced')
|
|
95
|
+
if (resolved.kind !== 'unpriced')
|
|
96
|
+
throw new Error(`Expected an unpriced result, received ${resolved.kind}`)
|
|
97
|
+
expect(resolved.reason).toContain(reason)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('computes provider-usage cost with fixed-point arithmetic, never JS float money', () => {
|
|
101
|
+
const cost = priceOfficialDeepSeekUsage({
|
|
102
|
+
provider: 'deepseek-official',
|
|
103
|
+
baseUrlClass: 'official-public',
|
|
104
|
+
apiRoute: 'chat-completions',
|
|
105
|
+
modelId: 'deepseek-v4-flash',
|
|
106
|
+
currency: 'USD',
|
|
107
|
+
startedAt: new Date('2026-08-25T01:00:00.000Z'),
|
|
108
|
+
completedAt: new Date('2026-08-25T01:01:00.000Z'),
|
|
109
|
+
usage: { cacheReadTokens: 1, cacheMissTokens: 2, outputTokens: 3 },
|
|
110
|
+
})
|
|
111
|
+
expect(cost).toEqual({
|
|
112
|
+
kind: 'exact',
|
|
113
|
+
currency: 'USD',
|
|
114
|
+
band: 'peak',
|
|
115
|
+
femtoUnits: '4854000000',
|
|
116
|
+
decimal: '0.000004854',
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('returns a range when one request spans a published price-band boundary', () => {
|
|
121
|
+
const cost = priceOfficialDeepSeekUsage({
|
|
122
|
+
provider: 'deepseek-official',
|
|
123
|
+
baseUrlClass: 'official-public',
|
|
124
|
+
apiRoute: 'chat-completions',
|
|
125
|
+
modelId: 'deepseek-v4-pro',
|
|
126
|
+
currency: 'CNY',
|
|
127
|
+
startedAt: new Date('2026-08-25T03:59:59.000Z'),
|
|
128
|
+
completedAt: new Date('2026-08-25T04:00:01.000Z'),
|
|
129
|
+
usage: { cacheReadTokens: 10, cacheMissTokens: 20, outputTokens: 3 },
|
|
130
|
+
})
|
|
131
|
+
expect(cost).toMatchObject({
|
|
132
|
+
kind: 'range', currency: 'CNY', bands: ['peak', 'off-peak'],
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('returns a range when both endpoints share a band but the interval crosses peak', () => {
|
|
137
|
+
const cost = priceOfficialDeepSeekUsage({
|
|
138
|
+
provider: 'deepseek-official',
|
|
139
|
+
baseUrlClass: 'official-public',
|
|
140
|
+
apiRoute: 'chat-completions',
|
|
141
|
+
modelId: 'deepseek-v4-flash',
|
|
142
|
+
currency: 'USD',
|
|
143
|
+
startedAt: new Date('2026-08-25T00:59:59.000Z'),
|
|
144
|
+
completedAt: new Date('2026-08-25T04:00:01.000Z'),
|
|
145
|
+
usage: { cacheReadTokens: 10, cacheMissTokens: 20, outputTokens: 3 },
|
|
146
|
+
})
|
|
147
|
+
expect(cost).toMatchObject({
|
|
148
|
+
kind: 'range', currency: 'USD', bands: ['off-peak', 'peak'],
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it.each([
|
|
153
|
+
['Friday through Monday', '2026-08-28T10:00:01.000Z', '2026-08-31T01:00:01.000Z'],
|
|
154
|
+
['seven full days', '2026-08-25T00:00:00.000Z', '2026-09-01T00:00:00.000Z'],
|
|
155
|
+
])('returns a range across %s', (_label, startedAt, completedAt) => {
|
|
156
|
+
expect(priceOfficialDeepSeekUsage({
|
|
157
|
+
provider: 'deepseek-official',
|
|
158
|
+
baseUrlClass: 'official-public',
|
|
159
|
+
apiRoute: 'responses',
|
|
160
|
+
modelId: 'deepseek-v4-flash',
|
|
161
|
+
currency: 'USD',
|
|
162
|
+
startedAt: new Date(startedAt),
|
|
163
|
+
completedAt: new Date(completedAt),
|
|
164
|
+
usage: { cacheReadTokens: 10, cacheMissTokens: 20, outputTokens: 3 },
|
|
165
|
+
})).toMatchObject({ kind: 'range', currency: 'USD' })
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it.each([
|
|
169
|
+
['invalid start timestamp', new Date('invalid'), new Date('2026-08-25T01:00:00.000Z'), 'timestamp'],
|
|
170
|
+
['completion before start', new Date('2026-08-25T01:00:01.000Z'), new Date('2026-08-25T01:00:00.000Z'), 'precedes'],
|
|
171
|
+
] as const)('fails closed for %s', (_label, startedAt, completedAt, reason) => {
|
|
172
|
+
const cost = priceOfficialDeepSeekUsage({
|
|
173
|
+
provider: 'deepseek-official',
|
|
174
|
+
baseUrlClass: 'official-public',
|
|
175
|
+
apiRoute: 'chat-completions',
|
|
176
|
+
modelId: 'deepseek-v4-flash',
|
|
177
|
+
currency: 'USD',
|
|
178
|
+
startedAt,
|
|
179
|
+
completedAt,
|
|
180
|
+
usage: { cacheReadTokens: 1, cacheMissTokens: 2, outputTokens: 3 },
|
|
181
|
+
})
|
|
182
|
+
expect(cost.kind).toBe('unpriced')
|
|
183
|
+
if (cost.kind !== 'unpriced') throw new Error('expected unpriced interval')
|
|
184
|
+
expect(cost.reason).toContain(reason)
|
|
185
|
+
})
|
|
186
|
+
})
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** Registry behavior for the bundled official DeepSeek tokenizer artifacts. */
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { describe, expect, it } from 'vitest'
|
|
6
|
+
import {
|
|
7
|
+
DEEPSEEK_V4_TOKENIZER_ARTIFACT,
|
|
8
|
+
DEEPSEEK_VISION_TOKENIZER_ARTIFACT,
|
|
9
|
+
createDeepSeekV4TokenizerFromAssets,
|
|
10
|
+
deepSeekV4TokenizerFailureReason,
|
|
11
|
+
deepSeekV4TokenizerForModel,
|
|
12
|
+
} from '../../src/deepseek-v4-tokenizer.ts'
|
|
13
|
+
|
|
14
|
+
const RUNTIME_ROOT = new URL('../../', import.meta.url)
|
|
15
|
+
|
|
16
|
+
describe('bundled DeepSeek tokenizer artifact registry', () => {
|
|
17
|
+
it('resolves each exact API model id to its pinned artifact identity', () => {
|
|
18
|
+
for (const modelId of ['deepseek-v4-flash', 'deepseek-v4-pro']) {
|
|
19
|
+
expect(deepSeekV4TokenizerForModel(modelId)?.countText('identity probe')).toMatchObject({
|
|
20
|
+
kind: 'exact-tokenizer',
|
|
21
|
+
tokenizerId: 'deepseek-ai/DeepSeek-V4-Pro',
|
|
22
|
+
tokenizerRevision: DEEPSEEK_V4_TOKENIZER_ARTIFACT.revision,
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
expect(deepSeekV4TokenizerForModel('deepseek-v4-flash-vision-exp')?.countText('identity probe')).toMatchObject({
|
|
26
|
+
kind: 'exact-tokenizer',
|
|
27
|
+
tokenizerId: 'deepseek-ai/DeepSeek-V4-Flash-Vision-Exp',
|
|
28
|
+
tokenizerRevision: '6821d6ad3681a4b137b066b76094fa82ebd0a380',
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('ships a vision tokenizer that is not an alias of the V4 Pro tokenizer', () => {
|
|
33
|
+
expect(DEEPSEEK_VISION_TOKENIZER_ARTIFACT.modelIds).toEqual(['deepseek-v4-flash-vision-exp'])
|
|
34
|
+
expect(DEEPSEEK_VISION_TOKENIZER_ARTIFACT.license).toBe('MIT')
|
|
35
|
+
expect(DEEPSEEK_VISION_TOKENIZER_ARTIFACT.tokenizerSha256)
|
|
36
|
+
.not.toBe(DEEPSEEK_V4_TOKENIZER_ARTIFACT.tokenizerSha256)
|
|
37
|
+
// The official vision placeholder special token exists only in the vision
|
|
38
|
+
// vocabulary; the text tokenizer must not silently accept it as one token.
|
|
39
|
+
expect(deepSeekV4TokenizerForModel('deepseek-v4-flash-vision-exp')?.countText('<|deepseek_image|>').tokens).toBe(1)
|
|
40
|
+
expect(deepSeekV4TokenizerForModel('deepseek-v4-pro')?.countText('<|deepseek_image|>').tokens).toBeGreaterThan(1)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it.each([
|
|
44
|
+
'deepseek-v4-flash-vision',
|
|
45
|
+
'deepseek-v4-flash-vision-exp-2',
|
|
46
|
+
'DeepSeek-V4-Flash-Vision-Exp',
|
|
47
|
+
'deepseek-v4-vision-exp',
|
|
48
|
+
'deepseek-v4-pro-vision',
|
|
49
|
+
'deepseek-vision',
|
|
50
|
+
'gpt-4o',
|
|
51
|
+
'',
|
|
52
|
+
])('refuses the non-exact model id %s', (modelId) => {
|
|
53
|
+
expect(deepSeekV4TokenizerForModel(modelId)).toBeUndefined()
|
|
54
|
+
expect(deepSeekV4TokenizerFailureReason(modelId)).toBeUndefined()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('reports the cached failure reason for the artifact serving one model', () => {
|
|
58
|
+
// Both text models resolve successfully today, so no failure is recorded.
|
|
59
|
+
expect(deepSeekV4TokenizerFailureReason('deepseek-v4-pro')).toBeUndefined()
|
|
60
|
+
expect(deepSeekV4TokenizerFailureReason('deepseek-v4-flash-vision-exp')).toBeUndefined()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('fails closed on byte-length, SHA-256, and JSON corruption for the vision artifact without touching the text artifact', () => {
|
|
64
|
+
const visionRoot = new URL('assets/deepseek-v4-vision-exp/', RUNTIME_ROOT)
|
|
65
|
+
const tokenizerBytes = readFileSync(fileURLToPath(new URL('tokenizer.json', visionRoot)))
|
|
66
|
+
const good = {
|
|
67
|
+
tokenizer: { bytes: tokenizerBytes.byteLength, sha256: DEEPSEEK_VISION_TOKENIZER_ARTIFACT.tokenizerSha256 },
|
|
68
|
+
config: { bytes: 801, sha256: DEEPSEEK_VISION_TOKENIZER_ARTIFACT.tokenizerConfigSha256 },
|
|
69
|
+
}
|
|
70
|
+
expect(() => createDeepSeekV4TokenizerFromAssets(visionRoot, {
|
|
71
|
+
tokenizer: { ...good.tokenizer, bytes: good.tokenizer.bytes + 1 },
|
|
72
|
+
config: good.config,
|
|
73
|
+
})).toThrow(/bytes/)
|
|
74
|
+
expect(() => createDeepSeekV4TokenizerFromAssets(visionRoot, {
|
|
75
|
+
tokenizer: { ...good.tokenizer, sha256: '0'.repeat(64) },
|
|
76
|
+
config: good.config,
|
|
77
|
+
})).toThrow(/SHA-256/)
|
|
78
|
+
expect(() => createDeepSeekV4TokenizerFromAssets(visionRoot, good)).not.toThrow()
|
|
79
|
+
// The shared registry still serves the independent text artifact.
|
|
80
|
+
expect(deepSeekV4TokenizerForModel('deepseek-v4-flash')?.countText('still exact').kind).toBe('exact-tokenizer')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('fails closed when a byte-and-hash-valid asset is not JSON', async () => {
|
|
84
|
+
const { mkdtemp, writeFile } = await import('node:fs/promises')
|
|
85
|
+
const { tmpdir } = await import('node:os')
|
|
86
|
+
const { createHash } = await import('node:crypto')
|
|
87
|
+
const { join } = await import('node:path')
|
|
88
|
+
const root = await mkdtemp(join(tmpdir(), 'dsh-tokenizer-json-corrupt-'))
|
|
89
|
+
try {
|
|
90
|
+
const payload = 'this is definitely not json'
|
|
91
|
+
const bytes = Buffer.from(payload, 'utf8')
|
|
92
|
+
await writeFile(join(root, 'tokenizer.json'), bytes)
|
|
93
|
+
await writeFile(join(root, 'tokenizer_config.json'), bytes)
|
|
94
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex')
|
|
95
|
+
const integrity = {
|
|
96
|
+
tokenizer: { bytes: bytes.byteLength, sha256 },
|
|
97
|
+
config: { bytes: bytes.byteLength, sha256 },
|
|
98
|
+
}
|
|
99
|
+
await expect(Promise.resolve().then(() =>
|
|
100
|
+
createDeepSeekV4TokenizerFromAssets(new URL(`file://${root}/`), integrity),
|
|
101
|
+
)).rejects.toThrow(/JSON|json/)
|
|
102
|
+
} finally {
|
|
103
|
+
await import('node:fs/promises').then(fs => fs.rm(root, { recursive: true, force: true }))
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('verifies the committed asset manifests against the shipped files', () => {
|
|
108
|
+
for (const [directory, artifact] of [
|
|
109
|
+
['deepseek-v4', DEEPSEEK_V4_TOKENIZER_ARTIFACT],
|
|
110
|
+
['deepseek-v4-vision-exp', DEEPSEEK_VISION_TOKENIZER_ARTIFACT],
|
|
111
|
+
] as const) {
|
|
112
|
+
const manifest = JSON.parse(
|
|
113
|
+
readFileSync(fileURLToPath(new URL(`assets/${directory}/manifest.json`, RUNTIME_ROOT)), 'utf8'),
|
|
114
|
+
) as { repository: string, revision: string, modelIds: string[], files: Record<string, { bytes: number, sha256: string }> }
|
|
115
|
+
expect(manifest.repository).toBe(artifact.repository)
|
|
116
|
+
expect(manifest.revision).toBe(artifact.revision)
|
|
117
|
+
expect(manifest.modelIds).toEqual([...artifact.modelIds])
|
|
118
|
+
expect(manifest.files['tokenizer.json']?.sha256).toBe(artifact.tokenizerSha256)
|
|
119
|
+
expect(manifest.files['tokenizer_config.json']?.sha256).toBe(artifact.tokenizerConfigSha256)
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
})
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** Node vision-token arithmetic against the official DeepSeek reference fixtures. */
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { describe, expect, it } from 'vitest'
|
|
6
|
+
import {
|
|
7
|
+
DEEPSEEK_VISION_DEFAULT_IMAGE_TOKENS,
|
|
8
|
+
DEEPSEEK_VISION_IMAGE_ESTIMATOR,
|
|
9
|
+
DEEPSEEK_VISION_PROJECTION,
|
|
10
|
+
deepSeekVisionImageGrid,
|
|
11
|
+
deepSeekVisionImageTokens,
|
|
12
|
+
estimateDeepSeekVisionImageTokens,
|
|
13
|
+
isWithinDeepSeekRequestPixelBudget,
|
|
14
|
+
} from '../../src/runtime/deepseek-v4-vision-tokens.ts'
|
|
15
|
+
|
|
16
|
+
interface GoldenEntry {
|
|
17
|
+
readonly width: number
|
|
18
|
+
readonly height: number
|
|
19
|
+
readonly startTokenPos: number
|
|
20
|
+
readonly nLlmH: number
|
|
21
|
+
readonly nLlmW: number
|
|
22
|
+
readonly tokens: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface GoldenFixture {
|
|
26
|
+
readonly source: { readonly repository: string, readonly revision: string }
|
|
27
|
+
readonly parameters: Readonly<Record<string, number>>
|
|
28
|
+
readonly singleImages: readonly { width: number, height: number, nLlmH: number, nLlmW: number, tokensAtStart0: number }[]
|
|
29
|
+
readonly startPositions: readonly GoldenEntry[]
|
|
30
|
+
readonly sequences: readonly { entries: readonly GoldenEntry[] }[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const fixture = JSON.parse(readFileSync(
|
|
34
|
+
fileURLToPath(new URL('fixtures/vision-golden.json', import.meta.url)),
|
|
35
|
+
'utf8',
|
|
36
|
+
)) as GoldenFixture
|
|
37
|
+
|
|
38
|
+
describe('DeepSeek vision token arithmetic matches the official reference', () => {
|
|
39
|
+
it('pins the official projection parameters and revision', () => {
|
|
40
|
+
expect(DEEPSEEK_VISION_PROJECTION.sourceRepository).toBe(fixture.source.repository)
|
|
41
|
+
expect(DEEPSEEK_VISION_PROJECTION.sourceRevision).toBe(fixture.source.revision)
|
|
42
|
+
expect(DEEPSEEK_VISION_PROJECTION.visionPatchSize).toBe(fixture.parameters.visionPatchSize)
|
|
43
|
+
expect(DEEPSEEK_VISION_PROJECTION.visionDownsampleRatio).toBe(fixture.parameters.visionDownsampleRatio)
|
|
44
|
+
expect(DEEPSEEK_VISION_PROJECTION.visionMaxNTokens).toBe(fixture.parameters.visionMaxNTokens)
|
|
45
|
+
expect(DEEPSEEK_VISION_PROJECTION.visionMinPixels).toBe(fixture.parameters.visionMinPixels)
|
|
46
|
+
expect(DEEPSEEK_VISION_PROJECTION.visionMaxWhRatio).toBe(fixture.parameters.visionMaxWhRatio)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it.each(fixture.singleImages)('expands $width×$height to the official grid', (entry) => {
|
|
50
|
+
const grid = deepSeekVisionImageGrid(entry.width, entry.height)
|
|
51
|
+
expect(grid).toMatchObject({ nLlmH: entry.nLlmH, nLlmW: entry.nLlmW })
|
|
52
|
+
expect(deepSeekVisionImageTokens(entry.width, entry.height, 0)).toBe(entry.tokensAtStart0)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it.each(fixture.startPositions)('aligns 640×480 at start $startTokenPos to $tokens tokens', (entry) => {
|
|
56
|
+
const grid = deepSeekVisionImageGrid(entry.width, entry.height)
|
|
57
|
+
expect(grid.nLlmH).toBe(entry.nLlmH)
|
|
58
|
+
expect(grid.nLlmW).toBe(entry.nLlmW)
|
|
59
|
+
expect(deepSeekVisionImageTokens(entry.width, entry.height, entry.startTokenPos)).toBe(entry.tokens)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('reproduces multi-image sequences with accumulated start positions', () => {
|
|
63
|
+
for (const sequence of fixture.sequences) {
|
|
64
|
+
let position = sequence.entries[0]?.startTokenPos ?? 0
|
|
65
|
+
for (const entry of sequence.entries) {
|
|
66
|
+
expect(position).toBe(entry.startTokenPos)
|
|
67
|
+
const grid = deepSeekVisionImageGrid(entry.width, entry.height)
|
|
68
|
+
expect(grid.nLlmH).toBe(entry.nLlmH)
|
|
69
|
+
expect(grid.nLlmW).toBe(entry.nLlmW)
|
|
70
|
+
const tokens = deepSeekVisionImageTokens(entry.width, entry.height, position)
|
|
71
|
+
expect(tokens).toBe(entry.tokens)
|
|
72
|
+
position += tokens
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('reports the adapter default request pixel-budget boundary', () => {
|
|
78
|
+
expect(isWithinDeepSeekRequestPixelBudget(640, 480)).toBe(true)
|
|
79
|
+
expect(isWithinDeepSeekRequestPixelBudget(800, 800)).toBe(true) // 640000 exactly
|
|
80
|
+
expect(isWithinDeepSeekRequestPixelBudget(801, 800)).toBe(false) // one pixel over
|
|
81
|
+
expect(isWithinDeepSeekRequestPixelBudget(4096, 4096)).toBe(false)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('estimates valid images at the midpoint of all alignment residues', () => {
|
|
85
|
+
const estimate = estimateDeepSeekVisionImageTokens(512, 512)
|
|
86
|
+
const counts = [0, 1, 2, 3].map(position => deepSeekVisionImageTokens(512, 512, position))
|
|
87
|
+
expect(estimate).toEqual({
|
|
88
|
+
tokens: Math.round((Math.min(...counts) + Math.max(...counts)) / 2),
|
|
89
|
+
upperBoundTokens: DEEPSEEK_VISION_PROJECTION.visionMaxNTokens,
|
|
90
|
+
source: 'intrinsic-grid',
|
|
91
|
+
paddingMinimumTokens: Math.min(...counts),
|
|
92
|
+
paddingMaximumTokens: Math.max(...counts),
|
|
93
|
+
})
|
|
94
|
+
expect(Math.max(...counts) - Math.min(...counts)).toBeLessThanOrEqual(3)
|
|
95
|
+
expect(DEEPSEEK_VISION_IMAGE_ESTIMATOR.revision).toContain(fixture.source.revision)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('keeps alignment-only estimation error below ten percent across the official fixture sizes', () => {
|
|
99
|
+
for (const entry of fixture.singleImages) {
|
|
100
|
+
const estimate = estimateDeepSeekVisionImageTokens(entry.width, entry.height)
|
|
101
|
+
expect(estimate.source).toBe('intrinsic-grid')
|
|
102
|
+
for (let position = 0; position < 4; position += 1) {
|
|
103
|
+
const actual = deepSeekVisionImageTokens(entry.width, entry.height, position)
|
|
104
|
+
expect(Math.abs(estimate.tokens - actual) / actual).toBeLessThan(0.1)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it.each([
|
|
110
|
+
[0, 480],
|
|
111
|
+
[640.5, 480],
|
|
112
|
+
[Number.NaN, 480],
|
|
113
|
+
[Number.POSITIVE_INFINITY, 480],
|
|
114
|
+
[Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
|
|
115
|
+
])('uses the fixed fallback for unusable metadata %s×%s', (width, height) => {
|
|
116
|
+
expect(estimateDeepSeekVisionImageTokens(width, height)).toEqual({
|
|
117
|
+
tokens: DEEPSEEK_VISION_DEFAULT_IMAGE_TOKENS,
|
|
118
|
+
upperBoundTokens: DEEPSEEK_VISION_PROJECTION.visionMaxNTokens,
|
|
119
|
+
source: 'default',
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
})
|