codeep 2.17.0 → 2.18.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/dist/api/index.js +7 -1
- package/dist/config/providers.d.ts +9 -0
- package/dist/config/providers.js +69 -17
- package/dist/renderer/App.js +8 -2
- package/dist/renderer/commands/helpers.d.ts +2 -0
- package/dist/renderer/commands/helpers.js +16 -2
- package/dist/renderer/components/Status.d.ts +6 -0
- package/dist/renderer/main.js +2 -0
- package/dist/utils/tokenTracker.d.ts +14 -1
- package/dist/utils/tokenTracker.js +72 -12
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
package/dist/api/index.js
CHANGED
|
@@ -343,7 +343,13 @@ async function chatOpenAI(message, history, model, apiKey, onChunk, abortSignal)
|
|
|
343
343
|
let baseUrl = resolveBaseUrl(providerId, 'openai');
|
|
344
344
|
const authHeader = getProviderAuthHeader(providerId, 'openai');
|
|
345
345
|
const useCompletionTokens = usesMaxCompletionTokens(providerId);
|
|
346
|
-
|
|
346
|
+
// Two independent reasons to leave temperature out, and both must be checked
|
|
347
|
+
// here: the provider-level flag (endpoints that only accept the default) and
|
|
348
|
+
// the per-model one (generations that removed the sampling params outright,
|
|
349
|
+
// e.g. gemini-3.7-flash). Testing only the provider flag let a rejecting
|
|
350
|
+
// model through, because every model this rule covers is served over the
|
|
351
|
+
// OpenAI-compatible path, not the Anthropic one.
|
|
352
|
+
const omitTemperature = requiresDefaultTemperature(providerId) || modelRejectsSamplingParams(model);
|
|
347
353
|
if (!baseUrl) {
|
|
348
354
|
throw new Error(`Provider ${providerId} does not support OpenAI protocol`);
|
|
349
355
|
}
|
|
@@ -34,6 +34,10 @@ export interface ProviderConfig {
|
|
|
34
34
|
subscribeUrl?: string;
|
|
35
35
|
noApiKey?: boolean;
|
|
36
36
|
dynamicModels?: boolean;
|
|
37
|
+
/** Billed as a flat subscription (or free) — token counts are real, per-token
|
|
38
|
+
* cost is not. Cost surfaces must say "included in plan" instead of pricing
|
|
39
|
+
* these tokens at the provider's pay-per-use rates. */
|
|
40
|
+
flatFee?: boolean;
|
|
37
41
|
groupLabel?: string;
|
|
38
42
|
hint?: string;
|
|
39
43
|
mcpEndpoints?: {
|
|
@@ -60,6 +64,11 @@ export declare function getProviderModels(providerId: string): {
|
|
|
60
64
|
}[];
|
|
61
65
|
export declare function isNoApiKeyProvider(providerId: string): boolean;
|
|
62
66
|
export declare function isDynamicModelsProvider(providerId: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Returns true if the provider bills a flat subscription (or is free), so any
|
|
69
|
+
* per-token dollar figure we compute for it is invented — see `flatFee`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function isFlatFeeProvider(providerId: string): boolean;
|
|
63
72
|
export declare function getProviderBaseUrl(providerId: string, protocol: 'openai' | 'anthropic'): string | null;
|
|
64
73
|
export declare function getProviderAuthHeader(providerId: string, protocol: 'openai' | 'anthropic'): 'Bearer' | 'x-api-key';
|
|
65
74
|
export declare function getProviderMcpEndpoints(providerId: string): ProviderConfig['mcpEndpoints'] | null;
|
package/dist/config/providers.js
CHANGED
|
@@ -18,15 +18,19 @@ export const PROVIDERS = {
|
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
models: [
|
|
21
|
-
|
|
21
|
+
// GLM-5.3 is Coding-Plan only — the standalone pay-per-use API does not
|
|
22
|
+
// accept it yet, so it must NOT be copied into `z.ai-api`.
|
|
23
|
+
{ id: 'glm-5.3', name: 'GLM-5.3', description: 'Latest flagship for project-scale engineering (1M context)' },
|
|
24
|
+
{ id: 'glm-5.2', name: 'GLM-5.2', description: 'Previous flagship for project-scale engineering (1M context)' },
|
|
22
25
|
{ id: 'glm-5-turbo', name: 'GLM-5 Turbo', description: 'Fast GLM-5 variant, available to all users' },
|
|
23
26
|
],
|
|
24
|
-
defaultModel: 'glm-5.
|
|
27
|
+
defaultModel: 'glm-5.3',
|
|
25
28
|
defaultProtocol: 'openai',
|
|
26
29
|
maxOutputTokens: 131_072,
|
|
27
30
|
envKey: 'ZAI_API_KEY',
|
|
28
31
|
subscribeUrl: 'https://z.ai/subscribe?ic=NXYNXZOV14',
|
|
29
32
|
groupLabel: 'Z.AI — Subscription (GLM Coding Plan)',
|
|
33
|
+
flatFee: true,
|
|
30
34
|
hint: 'Uses your Z.AI subscription — no per-token charges.',
|
|
31
35
|
mcpEndpoints: {
|
|
32
36
|
webSearch: 'https://api.z.ai/api/mcp/web_search_prime/mcp',
|
|
@@ -81,6 +85,7 @@ export const PROVIDERS = {
|
|
|
81
85
|
envKey: 'ZAI_CN_API_KEY',
|
|
82
86
|
subscribeUrl: 'https://open.bigmodel.cn/glm-coding',
|
|
83
87
|
groupLabel: 'Z.AI China — Subscription (GLM Coding Plan)',
|
|
88
|
+
flatFee: true,
|
|
84
89
|
hint: 'Uses your ZhipuAI China subscription.',
|
|
85
90
|
mcpEndpoints: {
|
|
86
91
|
webSearch: 'https://open.bigmodel.cn/api/mcp/web_search_prime/mcp',
|
|
@@ -133,6 +138,7 @@ export const PROVIDERS = {
|
|
|
133
138
|
envKey: 'MINIMAX_API_KEY',
|
|
134
139
|
subscribeUrl: 'https://platform.minimax.io/subscribe/coding-plan?code=2lWvoWUhrp&source=link',
|
|
135
140
|
groupLabel: 'MiniMax — Subscription',
|
|
141
|
+
flatFee: true,
|
|
136
142
|
hint: 'Uses your MiniMax subscription — no per-token charges.',
|
|
137
143
|
},
|
|
138
144
|
'minimax-api': {
|
|
@@ -178,6 +184,7 @@ export const PROVIDERS = {
|
|
|
178
184
|
envKey: 'MINIMAX_CN_API_KEY',
|
|
179
185
|
subscribeUrl: 'https://platform.minimaxi.com',
|
|
180
186
|
groupLabel: 'MiniMax China — Subscription',
|
|
187
|
+
flatFee: true,
|
|
181
188
|
hint: 'Uses your MiniMax China subscription.',
|
|
182
189
|
},
|
|
183
190
|
'deepseek': {
|
|
@@ -230,6 +237,7 @@ export const PROVIDERS = {
|
|
|
230
237
|
envKey: 'KIMI_CODE_API_KEY',
|
|
231
238
|
subscribeUrl: 'https://www.kimi.com/code',
|
|
232
239
|
groupLabel: 'Kimi — Subscription (Kimi Code)',
|
|
240
|
+
flatFee: true,
|
|
233
241
|
hint: 'Uses your Kimi Code subscription — no per-token charges. Key from kimi.com/code/console.',
|
|
234
242
|
},
|
|
235
243
|
'kimi-api': {
|
|
@@ -283,10 +291,15 @@ export const PROVIDERS = {
|
|
|
283
291
|
openai: { baseUrl: 'https://api.x.ai/v1', authHeader: 'Bearer', supportsNativeTools: true },
|
|
284
292
|
},
|
|
285
293
|
models: [
|
|
286
|
-
{ id: 'grok-4.
|
|
294
|
+
{ id: 'grok-4.6', name: 'Grok 4.6', description: 'Flagship reasoning model — xAI recommends it for code, 500K context' },
|
|
295
|
+
{ id: 'grok-4.5', name: 'Grok 4.5', description: 'Previous flagship reasoning model, 500K context' },
|
|
287
296
|
{ id: 'grok-build-0.1', name: 'Grok Build 0.1', description: 'Agentic coding model — fast, 256K context' },
|
|
288
|
-
{ id: 'grok-4.3', name: 'Grok 4.3', description: '
|
|
297
|
+
{ id: 'grok-4.3', name: 'Grok 4.3', description: 'Older flagship, 1M context' },
|
|
289
298
|
],
|
|
299
|
+
// Stays on the agentic coder, not the new flagship. grok-4.6 is the better
|
|
300
|
+
// model and xAI recommends it for code, but it bills 2x input and 3x output
|
|
301
|
+
// against grok-build-0.1 — moving every unpinned user onto it silently is
|
|
302
|
+
// not ours to decide. It is one `/model` away for anyone who wants it.
|
|
290
303
|
defaultModel: 'grok-build-0.1',
|
|
291
304
|
defaultProtocol: 'openai',
|
|
292
305
|
useMaxCompletionTokens: true, // reasoning models reject max_tokens
|
|
@@ -317,6 +330,7 @@ export const PROVIDERS = {
|
|
|
317
330
|
envKey: 'BAILIAN_CODING_PLAN_API_KEY',
|
|
318
331
|
subscribeUrl: 'https://www.alibabacloud.com/help/en/model-studio/qwen-code-coding-plan',
|
|
319
332
|
groupLabel: 'Qwen — Subscription (Coding Plan)',
|
|
333
|
+
flatFee: true,
|
|
320
334
|
hint: 'Uses your Qwen Coding Plan — no per-token charges. sk-sp-… key from Model Studio. Interactive coding use only.',
|
|
321
335
|
},
|
|
322
336
|
'qwen-api': {
|
|
@@ -359,6 +373,7 @@ export const PROVIDERS = {
|
|
|
359
373
|
envKey: 'BAILIAN_TOKEN_PLAN_API_KEY',
|
|
360
374
|
subscribeUrl: 'https://modelstudio.console.alibabacloud.com/',
|
|
361
375
|
groupLabel: 'Qwen — Subscription (Token Plan)',
|
|
376
|
+
flatFee: true,
|
|
362
377
|
hint: 'Uses monthly Token Plan credits. Requires a separate sk-sp-… Token Plan key.',
|
|
363
378
|
},
|
|
364
379
|
'qwen-cn': {
|
|
@@ -379,6 +394,7 @@ export const PROVIDERS = {
|
|
|
379
394
|
envKey: 'BAILIAN_CODING_PLAN_CN_API_KEY',
|
|
380
395
|
subscribeUrl: 'https://bailian.console.aliyun.com/',
|
|
381
396
|
groupLabel: 'Qwen China — Subscription (Coding Plan)',
|
|
397
|
+
flatFee: true,
|
|
382
398
|
hint: 'Uses your Qwen Coding Plan (China). sk-sp-… key from Bailian.',
|
|
383
399
|
},
|
|
384
400
|
'qwen-cn-api': {
|
|
@@ -418,6 +434,7 @@ export const PROVIDERS = {
|
|
|
418
434
|
envKey: 'MODELSCOPE_API_KEY',
|
|
419
435
|
subscribeUrl: 'https://modelscope.cn/my/myaccesstoken',
|
|
420
436
|
groupLabel: 'ModelScope — Free (Qwen)',
|
|
437
|
+
flatFee: true,
|
|
421
438
|
hint: 'Fetches the live free catalog for your ModelScope token; availability and limits vary by account.',
|
|
422
439
|
},
|
|
423
440
|
'openai': {
|
|
@@ -478,7 +495,8 @@ export const PROVIDERS = {
|
|
|
478
495
|
},
|
|
479
496
|
models: [
|
|
480
497
|
{ id: 'gemini-3.1-pro-preview', name: 'Gemini 3.1 Pro', description: 'Most capable Gemini model' },
|
|
481
|
-
{ id: 'gemini-3.
|
|
498
|
+
{ id: 'gemini-3.7-flash', name: 'Gemini 3.7 Flash', description: 'Latest production Flash model — adjustable thinking, 64K output' },
|
|
499
|
+
{ id: 'gemini-3.6-flash', name: 'Gemini 3.6 Flash', description: 'Previous production Flash model' },
|
|
482
500
|
{ id: 'gemini-3.5-flash', name: 'Gemini 3.5 Flash', description: 'Stable frontier Flash model for coding and long agentic tasks' },
|
|
483
501
|
{ id: 'gemini-3.5-flash-lite', name: 'Gemini 3.5 Flash-Lite', description: 'Latest low-latency, low-cost workhorse' },
|
|
484
502
|
],
|
|
@@ -510,11 +528,11 @@ export const PROVIDERS = {
|
|
|
510
528
|
{ id: 'anthropic/claude-sonnet-5', name: 'Claude Sonnet 5', description: 'Anthropic — balanced' },
|
|
511
529
|
{ id: 'openai/gpt-5.6-sol', name: 'GPT-5.6 Sol', description: 'OpenAI — flagship' },
|
|
512
530
|
{ id: 'openai/gpt-5.6-luna', name: 'GPT-5.6 Luna', description: 'OpenAI — fast/efficient' },
|
|
513
|
-
{ id: 'google/gemini-3.
|
|
531
|
+
{ id: 'google/gemini-3.7-flash', name: 'Gemini 3.7 Flash', description: 'Google — latest production Flash' },
|
|
514
532
|
{ id: 'deepseek/deepseek-v4-pro', name: 'DeepSeek V4 Pro', description: 'DeepSeek — flagship agentic model' },
|
|
515
533
|
{ id: 'moonshotai/kimi-k3', name: 'Kimi K3', description: 'Moonshot — long-horizon coding' },
|
|
516
534
|
{ id: 'qwen/qwen3.8-max', name: 'Qwen 3.8 Max', description: 'Alibaba — latest flagship' },
|
|
517
|
-
{ id: 'x-ai/grok-4.
|
|
535
|
+
{ id: 'x-ai/grok-4.6', name: 'Grok 4.6', description: 'xAI — flagship reasoning' },
|
|
518
536
|
],
|
|
519
537
|
defaultModel: 'openrouter/auto',
|
|
520
538
|
defaultProtocol: 'openai',
|
|
@@ -695,6 +713,13 @@ export function isNoApiKeyProvider(providerId) {
|
|
|
695
713
|
export function isDynamicModelsProvider(providerId) {
|
|
696
714
|
return PROVIDERS[providerId]?.dynamicModels === true;
|
|
697
715
|
}
|
|
716
|
+
/**
|
|
717
|
+
* Returns true if the provider bills a flat subscription (or is free), so any
|
|
718
|
+
* per-token dollar figure we compute for it is invented — see `flatFee`.
|
|
719
|
+
*/
|
|
720
|
+
export function isFlatFeeProvider(providerId) {
|
|
721
|
+
return PROVIDERS[providerId]?.flatFee === true;
|
|
722
|
+
}
|
|
698
723
|
export function getProviderBaseUrl(providerId, protocol) {
|
|
699
724
|
const provider = PROVIDERS[providerId];
|
|
700
725
|
if (!provider)
|
|
@@ -745,14 +770,23 @@ export function providerNoStreamWithTools(providerId) {
|
|
|
745
770
|
* MODEL-level check, not a provider-level one (requiresDefaultTemperature
|
|
746
771
|
* can't express it). Omitting the field is always safe — the API treats
|
|
747
772
|
* omission as default. Kimi K2.x code/thinking models fix temperature
|
|
748
|
-
* internally and 400 on any custom value, so they're here too.
|
|
773
|
+
* internally and 400 on any custom value, so they're here too. Google removed
|
|
774
|
+
* the deprecated sampling parameters outright in the Gemini 3.7 generation.
|
|
749
775
|
*/
|
|
750
776
|
const SAMPLING_PARAMS_REJECTED = [
|
|
751
777
|
'claude-fable-5', 'claude-opus-5', 'claude-opus-4-8', 'claude-opus-4-7', 'claude-sonnet-5',
|
|
752
778
|
'kimi-k3', 'kimi-k2.7-code', 'kimi-for-coding', 'k3',
|
|
779
|
+
'gemini-3.7-flash',
|
|
753
780
|
];
|
|
754
781
|
export function modelRejectsSamplingParams(model) {
|
|
755
|
-
|
|
782
|
+
// Canonicalize both sides. OpenRouter routes these as `google/gemini-3.7-flash`
|
|
783
|
+
// and `anthropic/claude-opus-4.8`, which a raw comparison misses — so the model
|
|
784
|
+
// gets a temperature it rejects, on the one path where the id is namespaced.
|
|
785
|
+
const id = canonicalModelId(model);
|
|
786
|
+
return SAMPLING_PARAMS_REJECTED.some(entry => {
|
|
787
|
+
const canonical = canonicalModelId(entry);
|
|
788
|
+
return id === canonical || id.startsWith(`${canonical}-`);
|
|
789
|
+
});
|
|
756
790
|
}
|
|
757
791
|
/**
|
|
758
792
|
* Returns the effective max output tokens for a provider, capped by the provider's limit.
|
|
@@ -807,8 +841,9 @@ export function modelSupportsReasoningEffort(providerId, model) {
|
|
|
807
841
|
case 'z.ai-api':
|
|
808
842
|
case 'z.ai-cn':
|
|
809
843
|
case 'z.ai-cn-api':
|
|
810
|
-
// GLM-5.2 exposes graded High/Max effort;
|
|
811
|
-
|
|
844
|
+
// GLM-5.2 exposes graded High/Max effort; GLM-5.3 adds a distinct Low.
|
|
845
|
+
// Turbo is a plain toggle.
|
|
846
|
+
return idMatches(id, 'glm-5-2') || idMatches(id, 'glm-5-3');
|
|
812
847
|
case 'kimi':
|
|
813
848
|
return idMatches(id, 'k3');
|
|
814
849
|
case 'kimi-api':
|
|
@@ -854,14 +889,25 @@ export function reasoningParamsFor(providerId, model, tier) {
|
|
|
854
889
|
// none/low/medium/high/xhigh — no "max"; map our Max → xhigh (the ceiling).
|
|
855
890
|
return { reasoning_effort: tier === 'max' ? 'xhigh' : tier };
|
|
856
891
|
case 'google':
|
|
857
|
-
// Gemini
|
|
858
|
-
|
|
892
|
+
// Gemini's OpenAI-compat layer maps reasoning_effort onto thinking_level
|
|
893
|
+
// and documents low | medium | high. (Medium 400'd on Gemini 3 Preview,
|
|
894
|
+
// which is why this used to collapse it — that was a preview-era bug and
|
|
895
|
+
// is fixed.) 'max' has no Gemini equivalent, so it tops out at high.
|
|
896
|
+
// 'minimal' is deliberately not emitted: 3.7 Flash rejects it outright.
|
|
897
|
+
return { reasoning_effort: tier === 'max' ? 'high' : tier };
|
|
859
898
|
case 'deepseek':
|
|
899
|
+
// Graded thinking depth: high (default) or max. Lower tiers collapse to high.
|
|
900
|
+
return { reasoning_effort: tier === 'max' ? 'max' : 'high' };
|
|
860
901
|
case 'z.ai':
|
|
861
902
|
case 'z.ai-api':
|
|
862
903
|
case 'z.ai-cn':
|
|
863
904
|
case 'z.ai-cn-api':
|
|
864
|
-
//
|
|
905
|
+
// GLM-5.3 accepts low/high/max; GLM-5.2 grades only high|max, so lower
|
|
906
|
+
// tiers collapse to high there. Either way we always send an effort and
|
|
907
|
+
// never a disabled thinking block — GLM-5.3 rejects "disabled" outright.
|
|
908
|
+
if (idMatches(canonicalModelId(model), 'glm-5-3')) {
|
|
909
|
+
return { reasoning_effort: tier === 'low' ? 'low' : tier === 'max' ? 'max' : 'high' };
|
|
910
|
+
}
|
|
865
911
|
return { reasoning_effort: tier === 'max' ? 'max' : 'high' };
|
|
866
912
|
case 'kimi':
|
|
867
913
|
case 'kimi-api':
|
|
@@ -896,14 +942,20 @@ export function availableReasoningTiers(providerId, model) {
|
|
|
896
942
|
case 'openai':
|
|
897
943
|
return ['auto', 'low', 'medium', 'high', 'max'];
|
|
898
944
|
case 'google':
|
|
899
|
-
//
|
|
900
|
-
|
|
945
|
+
// low | medium | high, per Gemini's OpenAI-compat mapping table. Medium
|
|
946
|
+
// is 3.7 Flash's own default and the tier Google recommends for agentic
|
|
947
|
+
// coding, so collapsing it hid the setting most users want.
|
|
948
|
+
return ['auto', 'low', 'medium', 'high'];
|
|
901
949
|
case 'deepseek':
|
|
950
|
+
return ['auto', 'high', 'max'];
|
|
902
951
|
case 'z.ai':
|
|
903
952
|
case 'z.ai-api':
|
|
904
953
|
case 'z.ai-cn':
|
|
905
954
|
case 'z.ai-cn-api':
|
|
906
|
-
|
|
955
|
+
// GLM-5.3 distinguishes a Low tier; GLM-5.2 grades only high|max.
|
|
956
|
+
return idMatches(canonicalModelId(model), 'glm-5-3')
|
|
957
|
+
? ['auto', 'low', 'high', 'max']
|
|
958
|
+
: ['auto', 'high', 'max'];
|
|
907
959
|
case 'kimi':
|
|
908
960
|
case 'kimi-api':
|
|
909
961
|
case 'kimi-cn':
|
package/dist/renderer/App.js
CHANGED
|
@@ -2636,8 +2636,14 @@ export class App {
|
|
|
2636
2636
|
`runtime ${elapsed}`,
|
|
2637
2637
|
`tokens ${formatTokenCount(totalTokens)}`,
|
|
2638
2638
|
];
|
|
2639
|
-
|
|
2640
|
-
|
|
2639
|
+
// Only pay-per-use tokens carry a real price; flat-fee providers get the
|
|
2640
|
+
// short "in plan" wording so the segment can't crowd the right-edge hint.
|
|
2641
|
+
const billable = typeof stats.billableCost === 'number' ? stats.billableCost : (stats.estimatedCost ?? 0);
|
|
2642
|
+
if (billable > 0) {
|
|
2643
|
+
leftParts.push(`cost $${billable < 0.01 ? billable.toFixed(4) : billable.toFixed(2)}${stats.hasFlatFeeUsage ? ' + in plan' : ''}`);
|
|
2644
|
+
}
|
|
2645
|
+
else if (stats.hasFlatFeeUsage) {
|
|
2646
|
+
leftParts.push('cost in plan');
|
|
2641
2647
|
}
|
|
2642
2648
|
// Thinking-effort tier, same chip the compact fallback shows beside the
|
|
2643
2649
|
// model. Only present when set + supported — see getStatus.
|
|
@@ -77,6 +77,8 @@ export interface StatsTotals {
|
|
|
77
77
|
totalTokens: number;
|
|
78
78
|
totalPromptTokens: number;
|
|
79
79
|
totalCompletionTokens: number;
|
|
80
|
+
/** Raw sum across every entry. The report's total is derived from the
|
|
81
|
+
* breakdown instead, so flat-fee rows don't contribute dollars. */
|
|
80
82
|
estimatedCost: number;
|
|
81
83
|
}
|
|
82
84
|
export interface StatsCache {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* alongside `ctx.app.*` calls. Pulling them here gives them direct unit
|
|
8
8
|
* coverage.
|
|
9
9
|
*/
|
|
10
|
+
import { isFlatFeeProvider } from '../../config/providers.js';
|
|
10
11
|
/** Snippet window: chars of context before / after the match. */
|
|
11
12
|
export const SEARCH_SNIPPET_BEFORE = 30;
|
|
12
13
|
export const SEARCH_SNIPPET_AFTER = 50;
|
|
@@ -121,6 +122,10 @@ export function formatMemoryList(notes) {
|
|
|
121
122
|
export function formatModelCost(provider, estimatedCost) {
|
|
122
123
|
if (provider === 'ollama')
|
|
123
124
|
return 'free';
|
|
125
|
+
// Flat-fee providers meter real tokens but charge nothing per token — the
|
|
126
|
+
// dollar figure our pricing table computes for them is invented.
|
|
127
|
+
if (isFlatFeeProvider(provider))
|
|
128
|
+
return 'included in plan';
|
|
124
129
|
return estimatedCost > 0 ? `~$${estimatedCost.toFixed(4)}` : '(no pricing data)';
|
|
125
130
|
}
|
|
126
131
|
/**
|
|
@@ -143,11 +148,20 @@ export function formatStatsReport(args) {
|
|
|
143
148
|
lines.push(`- **${b.model}** (${b.provider}): ${fmt(b.promptTokens)} in / ${fmt(b.completionTokens)} out — ${costStr}`);
|
|
144
149
|
}
|
|
145
150
|
lines.push('');
|
|
151
|
+
// The total prices only pay-per-use rows; flat-fee rows are called out
|
|
152
|
+
// rather than folded in (or quietly dropped) — see formatModelCost.
|
|
153
|
+
const planRows = breakdown.filter(b => isFlatFeeProvider(b.provider));
|
|
154
|
+
const billable = breakdown
|
|
155
|
+
.filter(b => !isFlatFeeProvider(b.provider))
|
|
156
|
+
.reduce((s, b) => s + b.estimatedCost, 0);
|
|
146
157
|
if (currentProvider === 'ollama') {
|
|
147
158
|
lines.push(`**Total: free · ${fmt(totals.totalTokens)} tokens**`);
|
|
148
159
|
}
|
|
149
|
-
else if (
|
|
150
|
-
lines.push(`**Total:
|
|
160
|
+
else if (planRows.length === breakdown.length) {
|
|
161
|
+
lines.push(`**Total: included in plan · ${fmt(totals.totalTokens)} tokens**`);
|
|
162
|
+
}
|
|
163
|
+
else if (billable > 0) {
|
|
164
|
+
lines.push(`**Total: ~$${billable.toFixed(4)}${planRows.length > 0 ? ' + usage included in plan' : ''}**`);
|
|
151
165
|
}
|
|
152
166
|
}
|
|
153
167
|
if (cache.cacheReadTokens > 0 || cache.cacheCreationTokens > 0) {
|
|
@@ -21,6 +21,12 @@ export interface StatusInfo {
|
|
|
21
21
|
completionTokens: number;
|
|
22
22
|
requestCount: number;
|
|
23
23
|
estimatedCost?: number;
|
|
24
|
+
/** `estimatedCost` minus flat-fee entries — the only spend we may show in
|
|
25
|
+
* dollars (see providers.flatFee). */
|
|
26
|
+
billableCost?: number;
|
|
27
|
+
/** At least one entry came from a flat-fee provider, so the footer says
|
|
28
|
+
* "in plan" instead of pricing those tokens. */
|
|
29
|
+
hasFlatFeeUsage?: boolean;
|
|
24
30
|
};
|
|
25
31
|
}
|
|
26
32
|
/**
|
package/dist/renderer/main.js
CHANGED
|
@@ -111,6 +111,8 @@ function getStatus() {
|
|
|
111
111
|
completionTokens: stats.totalCompletionTokens,
|
|
112
112
|
requestCount: stats.requestCount,
|
|
113
113
|
estimatedCost: stats.estimatedCost,
|
|
114
|
+
billableCost: stats.billableCost,
|
|
115
|
+
hasFlatFeeUsage: stats.hasFlatFeeUsage,
|
|
114
116
|
},
|
|
115
117
|
};
|
|
116
118
|
}
|
|
@@ -19,6 +19,12 @@ export interface SessionTokenStats {
|
|
|
19
19
|
totalTokens: number;
|
|
20
20
|
requestCount: number;
|
|
21
21
|
estimatedCost: number;
|
|
22
|
+
/** `estimatedCost` minus every flat-fee entry — the only figure we may show
|
|
23
|
+
* as a dollar total, since flat-fee tokens carry no per-token charge. */
|
|
24
|
+
billableCost: number;
|
|
25
|
+
/** True when at least one entry came from a flat-fee provider, so callers can
|
|
26
|
+
* say "included in plan" instead of silently dropping that usage. */
|
|
27
|
+
hasFlatFeeUsage: boolean;
|
|
22
28
|
/** Anthropic prompt caching: total tokens written to cache this session. */
|
|
23
29
|
totalCacheCreationTokens: number;
|
|
24
30
|
/** Anthropic prompt caching: total tokens read from cache this session. */
|
|
@@ -105,8 +111,15 @@ export declare function getCostBreakdown(startIndex?: number): ProviderCostBreak
|
|
|
105
111
|
export interface CacheStats {
|
|
106
112
|
cacheCreationTokens: number;
|
|
107
113
|
cacheReadTokens: number;
|
|
108
|
-
/** Sum of estimatedSavings across
|
|
114
|
+
/** Sum of estimatedSavings across pay-per-use records only. */
|
|
109
115
|
estimatedSavingsUsd: number;
|
|
116
|
+
/** True when some cached tokens came from a flat-fee plan, whose "savings"
|
|
117
|
+
* are not a dollar amount at all. Lets the report say so instead of quoting
|
|
118
|
+
* a figure that silently covers only part of the session. */
|
|
119
|
+
hasFlatFeeCacheUsage: boolean;
|
|
120
|
+
/** True when EVERY cached token came from a flat-fee plan — there is no
|
|
121
|
+
* metered spend to have saved against. */
|
|
122
|
+
isEntirelyFlatFeeCache: boolean;
|
|
110
123
|
}
|
|
111
124
|
export declare function getCacheStats(): CacheStats;
|
|
112
125
|
/**
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
* Token and cost tracking for API usage
|
|
3
3
|
*/
|
|
4
4
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
5
|
+
import { isFlatFeeProvider } from '../config/providers.js';
|
|
5
6
|
import { formatResourceImpactReport } from './resourceImpact.js';
|
|
6
7
|
// Context window sizes per model (in tokens). Primarily mirrors providers.ts;
|
|
7
8
|
// retired aliases remain only where restored historical sessions still need a
|
|
8
9
|
// meaningful context/cost display.
|
|
9
10
|
const MODEL_CONTEXT_WINDOWS = {
|
|
10
11
|
// Z.AI / ZhipuAI
|
|
12
|
+
'glm-5.3': 1_000_000,
|
|
11
13
|
'glm-5.2': 1_000_000,
|
|
12
14
|
'glm-5.1': 200_000,
|
|
13
15
|
'glm-5-turbo': 202_752,
|
|
@@ -29,6 +31,7 @@ const MODEL_CONTEXT_WINDOWS = {
|
|
|
29
31
|
'deepseek-v4-flash': 1_000_000,
|
|
30
32
|
// Google
|
|
31
33
|
'gemini-3.1-pro-preview': 1_048_576,
|
|
34
|
+
'gemini-3.7-flash': 1_048_576,
|
|
32
35
|
'gemini-3.6-flash': 1_048_576,
|
|
33
36
|
'gemini-3.5-flash': 1_048_576,
|
|
34
37
|
'gemini-3.5-flash-lite': 1_048_576,
|
|
@@ -45,6 +48,7 @@ const MODEL_CONTEXT_WINDOWS = {
|
|
|
45
48
|
'k3': 1_000_000,
|
|
46
49
|
'k3-256k': 262_144,
|
|
47
50
|
// Grok (xAI)
|
|
51
|
+
'grok-4.6': 500_000,
|
|
48
52
|
'grok-4.5': 500_000,
|
|
49
53
|
'grok-build-0.1': 256_000,
|
|
50
54
|
'grok-4.3': 1_000_000,
|
|
@@ -72,6 +76,9 @@ export function getModelContextWindow(model) {
|
|
|
72
76
|
const MODEL_PRICING = {
|
|
73
77
|
// Z.AI / ZhipuAI
|
|
74
78
|
// Coding Plan is flat-fee; these official rates apply to pay-per-use.
|
|
79
|
+
// `glm-5.3` is GLM Coding Plan only — Z.AI publishes no per-token rate for it
|
|
80
|
+
// (the standalone model API is still "coming soon"), so it stays unpriced
|
|
81
|
+
// rather than borrowing GLM-5.2's.
|
|
75
82
|
'glm-5.2': { inputPer1M: 1.40, outputPer1M: 4.40 },
|
|
76
83
|
'glm-5.1': { inputPer1M: 1.40, outputPer1M: 4.40 },
|
|
77
84
|
'glm-5-turbo': { inputPer1M: 1.20, outputPer1M: 4.00 },
|
|
@@ -89,11 +96,20 @@ const MODEL_PRICING = {
|
|
|
89
96
|
'claude-sonnet-5': { inputPer1M: 3.00, outputPer1M: 15.00 },
|
|
90
97
|
'claude-haiku-4-5-20251001': { inputPer1M: 1.00, outputPer1M: 5.00 },
|
|
91
98
|
// DeepSeek (cache-miss input pricing)
|
|
99
|
+
// DeepSeek moved to peak / off-peak billing on 2026-08-16, with off-peak at
|
|
100
|
+
// half these rates. This table holds one rate per model and has no notion of
|
|
101
|
+
// wall-clock time, so it keeps the PEAK figures: an over-estimate is the
|
|
102
|
+
// honest direction for a cost estimate, and rule 5 of the catalogue policy
|
|
103
|
+
// allows a clearly-labelled conservative approximation but never an invented
|
|
104
|
+
// number. Cache-miss input; cache hits are ~1/50th and not modelled here.
|
|
92
105
|
'deepseek-v4-pro': { inputPer1M: 0.435, outputPer1M: 0.87 },
|
|
93
106
|
'deepseek-v4-flash': { inputPer1M: 0.14, outputPer1M: 0.28 },
|
|
94
107
|
// Google
|
|
108
|
+
// Gemini 3.6/3.7 Flash carry PROMOTIONAL rates that run through 2026-12-31 and
|
|
109
|
+
// step up to 1.50/7.50 on 2027-01-01 — revisit both rows on that date.
|
|
95
110
|
'gemini-3.1-pro-preview': { inputPer1M: 2.00, outputPer1M: 12.00 },
|
|
96
|
-
'gemini-3.
|
|
111
|
+
'gemini-3.7-flash': { inputPer1M: 0.75, outputPer1M: 3.75 },
|
|
112
|
+
'gemini-3.6-flash': { inputPer1M: 0.75, outputPer1M: 3.75 },
|
|
97
113
|
'gemini-3.5-flash': { inputPer1M: 1.50, outputPer1M: 9.00 },
|
|
98
114
|
'gemini-3.5-flash-lite': { inputPer1M: 0.30, outputPer1M: 2.50 },
|
|
99
115
|
'gemini-3-flash-preview': { inputPer1M: 0.50, outputPer1M: 3.00 },
|
|
@@ -110,7 +126,9 @@ const MODEL_PRICING = {
|
|
|
110
126
|
'kimi-for-coding-highspeed': { inputPer1M: 0.95, outputPer1M: 4.00 },
|
|
111
127
|
'k3': { inputPer1M: 3.00, outputPer1M: 15.00 },
|
|
112
128
|
'k3-256k': { inputPer1M: 3.00, outputPer1M: 15.00 },
|
|
113
|
-
// Grok (xAI)
|
|
129
|
+
// Grok (xAI) — base-tier rates. xAI doubles Grok 4.5/4.6 at prompts ≥200K;
|
|
130
|
+
// this table stores one flat rate per model, so the base tier is what we show.
|
|
131
|
+
'grok-4.6': { inputPer1M: 2.00, outputPer1M: 6.00 },
|
|
114
132
|
'grok-4.5': { inputPer1M: 2.00, outputPer1M: 6.00 },
|
|
115
133
|
'grok-build-0.1': { inputPer1M: 1.00, outputPer1M: 2.00 },
|
|
116
134
|
'grok-4.3': { inputPer1M: 1.25, outputPer1M: 2.50 },
|
|
@@ -262,9 +280,20 @@ export function getCacheStats() {
|
|
|
262
280
|
let cacheCreate = 0;
|
|
263
281
|
let cacheRead = 0;
|
|
264
282
|
let savings = 0;
|
|
283
|
+
let flatFeeCached = 0;
|
|
284
|
+
let meteredCached = 0;
|
|
265
285
|
for (const record of currentRecords()) {
|
|
286
|
+
const cached = (record.cacheCreationTokens ?? 0) + (record.cacheReadTokens ?? 0);
|
|
266
287
|
cacheCreate += record.cacheCreationTokens ?? 0;
|
|
267
288
|
cacheRead += record.cacheReadTokens ?? 0;
|
|
289
|
+
// A plan bills a flat fee, so caching saves latency but not money — pricing
|
|
290
|
+
// its cached tokens would invent a dollar figure the same way the per-model
|
|
291
|
+
// cost lines used to. Count the tokens (measured), skip the arithmetic.
|
|
292
|
+
if (isFlatFeeProvider(record.provider)) {
|
|
293
|
+
flatFeeCached += cached;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
meteredCached += cached;
|
|
268
297
|
// Savings = what cache-read tokens would have cost at full input rate,
|
|
269
298
|
// minus what they actually cost at 0.1×. (Cache creation is a slight
|
|
270
299
|
// *penalty* of 0.25× — netted in for honest reporting.)
|
|
@@ -275,7 +304,13 @@ export function getCacheStats() {
|
|
|
275
304
|
savings += cReadSaved - cCreateCost;
|
|
276
305
|
}
|
|
277
306
|
}
|
|
278
|
-
return {
|
|
307
|
+
return {
|
|
308
|
+
cacheCreationTokens: cacheCreate,
|
|
309
|
+
cacheReadTokens: cacheRead,
|
|
310
|
+
estimatedSavingsUsd: Math.max(0, savings),
|
|
311
|
+
hasFlatFeeCacheUsage: flatFeeCached > 0,
|
|
312
|
+
isEntirelyFlatFeeCache: flatFeeCached > 0 && meteredCached === 0,
|
|
313
|
+
};
|
|
279
314
|
}
|
|
280
315
|
/**
|
|
281
316
|
* Get session stats
|
|
@@ -293,13 +328,20 @@ export function getSessionStats() {
|
|
|
293
328
|
totalCacheCreationTokens += record.cacheCreationTokens ?? 0;
|
|
294
329
|
totalCacheReadTokens += record.cacheReadTokens ?? 0;
|
|
295
330
|
}
|
|
296
|
-
const
|
|
331
|
+
const breakdown = getCostBreakdown();
|
|
332
|
+
const estimatedCost = breakdown.reduce((s, b) => s + b.estimatedCost, 0);
|
|
333
|
+
const billableCost = breakdown
|
|
334
|
+
.filter(b => !isFlatFeeProvider(b.provider))
|
|
335
|
+
.reduce((s, b) => s + b.estimatedCost, 0);
|
|
336
|
+
const hasFlatFeeUsage = breakdown.some(b => isFlatFeeProvider(b.provider));
|
|
297
337
|
return {
|
|
298
338
|
totalPromptTokens,
|
|
299
339
|
totalCompletionTokens,
|
|
300
340
|
totalTokens,
|
|
301
341
|
requestCount: currentRecords().length,
|
|
302
342
|
estimatedCost,
|
|
343
|
+
billableCost,
|
|
344
|
+
hasFlatFeeUsage,
|
|
303
345
|
totalCacheCreationTokens,
|
|
304
346
|
totalCacheReadTokens,
|
|
305
347
|
};
|
|
@@ -350,36 +392,54 @@ export function formatCostReport() {
|
|
|
350
392
|
return '_No API requests in this session yet._';
|
|
351
393
|
}
|
|
352
394
|
const breakdown = getCostBreakdown();
|
|
395
|
+
// Flat-fee providers (subscriptions / free tiers) bill nothing per token, so
|
|
396
|
+
// their notional cost is never shown and never folded into the total. A
|
|
397
|
+
// session can mix them with pay-per-use models, so decide per entry.
|
|
398
|
+
const planEntries = breakdown.filter(b => isFlatFeeProvider(b.provider));
|
|
399
|
+
const costLine = planEntries.length === breakdown.length
|
|
400
|
+
? '**Estimated cost:** included in plan'
|
|
401
|
+
: `**Estimated cost:** $${stats.billableCost.toFixed(4)}${planEntries.length > 0 ? ' + usage included in plan' : ''}`;
|
|
353
402
|
const lines = [
|
|
354
403
|
'## Session Cost',
|
|
355
404
|
'',
|
|
356
405
|
`**Requests:** ${stats.requestCount} · **Input:** ${formatTokenCount(stats.totalPromptTokens)} · **Output:** ${formatTokenCount(stats.totalCompletionTokens)} · **Total:** ${formatTokenCount(stats.totalTokens)}`,
|
|
357
|
-
|
|
406
|
+
costLine,
|
|
358
407
|
'',
|
|
359
408
|
];
|
|
360
|
-
if (breakdown.length > 1 || (breakdown.length === 1 && breakdown[0].estimatedCost > 0)) {
|
|
409
|
+
if (breakdown.length > 1 || (breakdown.length === 1 && (breakdown[0].estimatedCost > 0 || planEntries.length > 0))) {
|
|
361
410
|
lines.push('| Provider / Model | Input | Output | Cost |');
|
|
362
411
|
lines.push('|---|---:|---:|---:|');
|
|
363
412
|
for (const b of breakdown) {
|
|
364
|
-
|
|
413
|
+
const cost = isFlatFeeProvider(b.provider) ? 'included in plan' : `$${b.estimatedCost.toFixed(4)}`;
|
|
414
|
+
lines.push(`| \`${b.provider}\` / \`${b.model}\` | ${formatTokenCount(b.promptTokens)} | ${formatTokenCount(b.completionTokens)} | ${cost} |`);
|
|
365
415
|
}
|
|
366
416
|
}
|
|
367
417
|
// Prompt caching summary — only shown if at least one cached call landed.
|
|
368
418
|
const cache = getCacheStats();
|
|
369
419
|
if (cache.cacheReadTokens > 0 || cache.cacheCreationTokens > 0) {
|
|
370
420
|
lines.push('', '### Prompt caching');
|
|
371
|
-
|
|
421
|
+
// The billing multipliers only describe a metered account. On a plan
|
|
422
|
+
// nothing is billed per token, so quoting a rate there would be as invented
|
|
423
|
+
// as the per-model prices this report already refuses to show.
|
|
424
|
+
const readNote = cache.isEntirelyFlatFeeCache ? '' : ' (billed at 0.1× input rate)';
|
|
425
|
+
const writeNote = cache.isEntirelyFlatFeeCache ? '' : ' (billed at 1.25× input rate)';
|
|
426
|
+
lines.push(`**Cache reads:** ${formatTokenCount(cache.cacheReadTokens)} tokens${readNote}`);
|
|
372
427
|
if (cache.cacheCreationTokens > 0) {
|
|
373
|
-
lines.push(`**Cache writes:** ${formatTokenCount(cache.cacheCreationTokens)} tokens
|
|
428
|
+
lines.push(`**Cache writes:** ${formatTokenCount(cache.cacheCreationTokens)} tokens${writeNote}`);
|
|
429
|
+
}
|
|
430
|
+
if (cache.isEntirelyFlatFeeCache) {
|
|
431
|
+
lines.push('**Savings:** caching saves latency, not money — this session is on a plan');
|
|
374
432
|
}
|
|
375
|
-
if (cache.estimatedSavingsUsd > 0) {
|
|
376
|
-
|
|
433
|
+
else if (cache.estimatedSavingsUsd > 0) {
|
|
434
|
+
// Name the partial coverage rather than letting one figure look total.
|
|
435
|
+
const scope = cache.hasFlatFeeCacheUsage ? ' (pay-per-use models only)' : '';
|
|
436
|
+
lines.push(`**Estimated savings vs no caching:** $${cache.estimatedSavingsUsd.toFixed(4)}${scope}`);
|
|
377
437
|
}
|
|
378
438
|
}
|
|
379
439
|
lines.push('', ...formatResourceImpactReport(stats.totalTokens));
|
|
380
440
|
// Models with no pricing entry don't contribute to cost — flag so users
|
|
381
441
|
// aren't surprised the total looks low.
|
|
382
|
-
const untracked = breakdown.filter(b => b.estimatedCost === 0 && (b.promptTokens + b.completionTokens) > 0);
|
|
442
|
+
const untracked = breakdown.filter(b => b.estimatedCost === 0 && (b.promptTokens + b.completionTokens) > 0 && !isFlatFeeProvider(b.provider));
|
|
383
443
|
if (untracked.length > 0) {
|
|
384
444
|
lines.push('', `_Note: ${untracked.length} model${untracked.length === 1 ? '' : 's'} (${untracked.map(u => `\`${u.model}\``).join(', ')}) have no pricing entry — token counts are tracked but not priced._`);
|
|
385
445
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "2.
|
|
1
|
+
export declare const VERSION = "2.18.1";
|
package/dist/version.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// AUTO-GENERATED by scripts/gen-version.js — do not edit by hand.
|
|
2
2
|
// Baked from package.json at build time so the bun-compiled binary reports
|
|
3
3
|
// the right version (it has no package.json on disk to read at runtime).
|
|
4
|
-
export const VERSION = '2.
|
|
4
|
+
export const VERSION = '2.18.1';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeep",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.1",
|
|
4
4
|
"description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"test:watch": "vitest",
|
|
17
17
|
"test:coverage": "vitest run --coverage",
|
|
18
18
|
"version": "node scripts/gen-version.js && git add src/version.ts",
|
|
19
|
+
"export:catalogue": "node --import tsx scripts/export-catalogue.ts",
|
|
19
20
|
"release": "node scripts/release.js"
|
|
20
21
|
},
|
|
21
22
|
"repository": {
|