@kkelly-offical/kkcode 0.1.7 → 0.2.3-preview.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/LICENSE +674 -674
- package/README.md +474 -387
- package/package.json +50 -46
- package/src/agent/agent.mjs +228 -220
- package/src/agent/custom-agent-loader.mjs +6 -3
- package/src/agent/generator.mjs +2 -2
- package/src/agent/prompt/assistant.txt +12 -0
- package/src/agent/prompt/bug-hunter.txt +89 -89
- package/src/agent/prompt/frontend-designer.txt +58 -58
- package/src/agent/prompt/guide.txt +1 -1
- package/src/agent/prompt/longagent-blueprint-agent.txt +83 -83
- package/src/agent/prompt/longagent-coding-agent.txt +37 -37
- package/src/agent/prompt/longagent-debugging-agent.txt +46 -46
- package/src/agent/prompt/longagent-preview-agent.txt +63 -63
- package/src/command/custom-commands.mjs +2 -2
- package/src/commands/agent.mjs +1 -1
- package/src/commands/background.mjs +145 -4
- package/src/commands/chat.mjs +117 -76
- package/src/commands/config.mjs +148 -1
- package/src/commands/doctor.mjs +30 -6
- package/src/commands/init.mjs +32 -6
- package/src/commands/longagent.mjs +117 -0
- package/src/commands/mcp.mjs +275 -43
- package/src/commands/permission.mjs +1 -1
- package/src/commands/session.mjs +195 -140
- package/src/commands/skill.mjs +63 -0
- package/src/commands/theme.mjs +1 -1
- package/src/commands/update.mjs +32 -0
- package/src/config/defaults.mjs +289 -260
- package/src/config/import-config.mjs +1 -1
- package/src/config/load-config.mjs +61 -4
- package/src/config/schema.mjs +604 -574
- package/src/context.mjs +4 -1
- package/src/core/constants.mjs +97 -91
- package/src/core/types.mjs +1 -1
- package/src/github/api.mjs +78 -78
- package/src/github/auth.mjs +294 -286
- package/src/github/flow.mjs +298 -298
- package/src/github/workspace.mjs +225 -212
- package/src/index.mjs +87 -82
- package/src/knowledge/frontend-aesthetics.txt +38 -38
- package/src/mcp/client-http.mjs +139 -141
- package/src/mcp/client-sse.mjs +297 -288
- package/src/mcp/client-stdio.mjs +534 -533
- package/src/mcp/constants.mjs +4 -2
- package/src/mcp/registry.mjs +498 -479
- package/src/mcp/stdio-framing.mjs +135 -133
- package/src/mcp/tool-result.mjs +24 -24
- package/src/observability/edit-diagnostics.mjs +449 -0
- package/src/observability/index.mjs +42 -42
- package/src/observability/metrics.mjs +165 -137
- package/src/observability/tracer.mjs +137 -137
- package/src/onboarding.mjs +209 -0
- package/src/orchestration/background-manager.mjs +567 -372
- package/src/orchestration/background-worker.mjs +419 -305
- package/src/orchestration/interruption-reason.mjs +21 -0
- package/src/orchestration/longagent-manager.mjs +197 -171
- package/src/orchestration/stage-scheduler.mjs +733 -728
- package/src/orchestration/subagent-router.mjs +7 -1
- package/src/orchestration/task-scheduler.mjs +219 -7
- package/src/permission/engine.mjs +1 -1
- package/src/permission/exec-policy.mjs +370 -370
- package/src/permission/file-edit-policy.mjs +108 -0
- package/src/permission/prompt.mjs +1 -1
- package/src/permission/rules.mjs +116 -7
- package/src/plugin/builtin-hooks/post-edit-format.mjs +2 -1
- package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +104 -40
- package/src/plugin/hook-bus.mjs +19 -5
- package/src/plugin/manifest-loader.mjs +222 -0
- package/src/provider/anthropic.mjs +396 -390
- package/src/provider/ollama.mjs +7 -1
- package/src/provider/openai.mjs +382 -340
- package/src/provider/retry-policy.mjs +74 -68
- package/src/provider/router.mjs +242 -241
- package/src/provider/sse.mjs +104 -104
- package/src/provider/wizard.mjs +556 -0
- package/src/repl/capability-facade.mjs +30 -0
- package/src/repl/command-surface.mjs +23 -0
- package/src/repl/controller-entry.mjs +40 -0
- package/src/repl/core-shell.mjs +208 -0
- package/src/repl/dialog-router.mjs +87 -0
- package/src/repl/input-engine.mjs +76 -0
- package/src/repl/keymap.mjs +7 -0
- package/src/repl/operator-surface.mjs +15 -0
- package/src/repl/permission-flow.mjs +49 -0
- package/src/repl/runtime-facade.mjs +36 -0
- package/src/repl/slash-router.mjs +62 -0
- package/src/repl/state-store.mjs +29 -0
- package/src/repl/turn-controller.mjs +58 -0
- package/src/repl/verification.mjs +23 -0
- package/src/repl.mjs +3371 -2981
- package/src/rules/load-rules.mjs +3 -3
- package/src/runtime.mjs +1 -1
- package/src/session/agent-transaction.mjs +86 -0
- package/src/session/checkpoint.mjs +302 -302
- package/src/session/compaction.mjs +298 -298
- package/src/session/engine.mjs +417 -232
- package/src/session/longagent-4stage.mjs +467 -460
- package/src/session/longagent-hybrid.mjs +1344 -1097
- package/src/session/longagent-plan.mjs +376 -365
- package/src/session/longagent-project-memory.mjs +53 -53
- package/src/session/longagent-scaffold.mjs +291 -291
- package/src/session/longagent-task-bus.mjs +138 -54
- package/src/session/longagent-utils.mjs +828 -472
- package/src/session/longagent.mjs +911 -900
- package/src/session/loop.mjs +1005 -930
- package/src/session/prompt/agent.txt +25 -25
- package/src/session/prompt/anthropic.txt +150 -150
- package/src/session/prompt/beast.txt +1 -1
- package/src/session/prompt/plan.txt +31 -31
- package/src/session/prompt/qwen.txt +46 -46
- package/src/session/recovery.mjs +21 -0
- package/src/session/rollback.mjs +196 -195
- package/src/session/routing-observability.mjs +72 -0
- package/src/session/runtime-state.mjs +47 -0
- package/src/session/store.mjs +523 -519
- package/src/session/system-prompt.mjs +308 -273
- package/src/session/task-validator.mjs +267 -267
- package/src/session/usability-gates.mjs +2 -2
- package/src/skill/builtin/commit.mjs +64 -64
- package/src/skill/builtin/design.mjs +76 -76
- package/src/skill/generator.mjs +18 -2
- package/src/skill/registry.mjs +642 -390
- package/src/storage/audit-store.mjs +18 -11
- package/src/storage/event-log.mjs +7 -1
- package/src/storage/ghost-commit-store.mjs +243 -245
- package/src/storage/paths.mjs +17 -0
- package/src/theme/default-theme.mjs +1 -1
- package/src/theme/markdown.mjs +4 -0
- package/src/theme/schema.mjs +1 -1
- package/src/theme/status-bar.mjs +162 -158
- package/src/tool/audit-wrapper.mjs +18 -2
- package/src/tool/edit-transaction.mjs +23 -0
- package/src/tool/executor.mjs +26 -1
- package/src/tool/file-read-state.mjs +65 -0
- package/src/tool/git-auto.mjs +526 -526
- package/src/tool/git-full-auto.mjs +487 -478
- package/src/tool/mutation-guard.mjs +54 -0
- package/src/tool/prompt/edit.txt +3 -3
- package/src/tool/prompt/multiedit.txt +1 -0
- package/src/tool/prompt/notebookedit.txt +2 -1
- package/src/tool/prompt/patch.txt +25 -24
- package/src/tool/prompt/read.txt +3 -3
- package/src/tool/prompt/sysinfo.txt +29 -0
- package/src/tool/prompt/task.txt +66 -4
- package/src/tool/prompt/write.txt +2 -2
- package/src/tool/question-prompt.mjs +99 -93
- package/src/tool/registry.mjs +1701 -1343
- package/src/tool/task-tool.mjs +14 -6
- package/src/ui/activity-renderer.mjs +667 -664
- package/src/ui/repl-background-panel.mjs +7 -0
- package/src/ui/repl-capability-panel.mjs +9 -0
- package/src/ui/repl-dashboard.mjs +54 -4
- package/src/ui/repl-help.mjs +110 -0
- package/src/ui/repl-operator-panel.mjs +12 -0
- package/src/ui/repl-route-feedback.mjs +35 -0
- package/src/ui/repl-status-view.mjs +76 -0
- package/src/ui/repl-task-panel.mjs +5 -0
- package/src/ui/repl-transcript-panel.mjs +56 -0
- package/src/ui/repl-turn-summary.mjs +135 -0
- package/src/update/checker.mjs +184 -0
- package/src/usage/pricing.mjs +122 -121
- package/src/usage/usage-meter.mjs +1 -0
- package/src/util/git.mjs +562 -519
- package/src/util/template.mjs +6 -1
- package/src/version.mjs +3 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { spawn } from "node:child_process"
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises"
|
|
3
|
+
import { dirname } from "node:path"
|
|
4
|
+
import { mkdir } from "node:fs/promises"
|
|
5
|
+
import { PACKAGE_NAME, PACKAGE_VERSION } from "../version.mjs"
|
|
6
|
+
import { updateStatePath } from "../storage/paths.mjs"
|
|
7
|
+
|
|
8
|
+
const DEFAULT_REGISTRY = "https://registry.npmjs.org"
|
|
9
|
+
const DEFAULT_TIMEOUT_MS = 2500
|
|
10
|
+
|
|
11
|
+
function normalizeRegistry(registry = DEFAULT_REGISTRY) {
|
|
12
|
+
return String(registry || DEFAULT_REGISTRY).replace(/\/+$/, "")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function encodePackageName(name) {
|
|
16
|
+
return String(name).replace("/", "%2F")
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseVersion(version) {
|
|
20
|
+
const match = String(version || "").trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/)
|
|
21
|
+
if (!match) return null
|
|
22
|
+
return {
|
|
23
|
+
major: Number(match[1]),
|
|
24
|
+
minor: Number(match[2]),
|
|
25
|
+
patch: Number(match[3]),
|
|
26
|
+
prerelease: match[4] ? match[4].split(".").map((part) => (/^\d+$/.test(part) ? Number(part) : part)) : []
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function compareIdentifier(a, b) {
|
|
31
|
+
if (a === b) return 0
|
|
32
|
+
const aNum = typeof a === "number"
|
|
33
|
+
const bNum = typeof b === "number"
|
|
34
|
+
if (aNum && bNum) return a > b ? 1 : -1
|
|
35
|
+
if (aNum) return -1
|
|
36
|
+
if (bNum) return 1
|
|
37
|
+
return String(a) > String(b) ? 1 : -1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function compareVersions(a, b) {
|
|
41
|
+
const av = parseVersion(a)
|
|
42
|
+
const bv = parseVersion(b)
|
|
43
|
+
if (!av || !bv) return String(a || "").localeCompare(String(b || ""))
|
|
44
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
45
|
+
if (av[key] !== bv[key]) return av[key] > bv[key] ? 1 : -1
|
|
46
|
+
}
|
|
47
|
+
const aPre = av.prerelease
|
|
48
|
+
const bPre = bv.prerelease
|
|
49
|
+
if (!aPre.length && !bPre.length) return 0
|
|
50
|
+
if (!aPre.length) return 1
|
|
51
|
+
if (!bPre.length) return -1
|
|
52
|
+
const len = Math.max(aPre.length, bPre.length)
|
|
53
|
+
for (let i = 0; i < len; i++) {
|
|
54
|
+
if (aPre[i] === undefined) return -1
|
|
55
|
+
if (bPre[i] === undefined) return 1
|
|
56
|
+
const cmp = compareIdentifier(aPre[i], bPre[i])
|
|
57
|
+
if (cmp !== 0) return cmp
|
|
58
|
+
}
|
|
59
|
+
return 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function updateConfig(config = {}) {
|
|
63
|
+
return {
|
|
64
|
+
enabled: config.update?.enabled !== false,
|
|
65
|
+
notifyOnStartup: config.update?.notify_on_startup !== false,
|
|
66
|
+
autoInstall: Boolean(config.update?.auto_install),
|
|
67
|
+
channel: config.update?.channel || "latest",
|
|
68
|
+
checkIntervalHours: Number(config.update?.check_interval_hours ?? 12),
|
|
69
|
+
registry: config.update?.registry || DEFAULT_REGISTRY,
|
|
70
|
+
timeoutMs: Number(config.update?.timeout_ms ?? DEFAULT_TIMEOUT_MS)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function readUpdateState(file = updateStatePath()) {
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(await readFile(file, "utf8"))
|
|
77
|
+
} catch {
|
|
78
|
+
return {}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function writeUpdateState(state, file = updateStatePath()) {
|
|
83
|
+
await mkdir(dirname(file), { recursive: true })
|
|
84
|
+
await writeFile(file, `${JSON.stringify(state, null, 2)}\n`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function fetchPackageMetadata({ packageName = PACKAGE_NAME, registry = DEFAULT_REGISTRY, timeoutMs = DEFAULT_TIMEOUT_MS, fetchImpl = globalThis.fetch } = {}) {
|
|
88
|
+
if (typeof fetchImpl !== "function") throw new Error("fetch is unavailable in this Node runtime")
|
|
89
|
+
const controller = new AbortController()
|
|
90
|
+
const timer = setTimeout(() => controller.abort(), Math.max(100, Number(timeoutMs || DEFAULT_TIMEOUT_MS)))
|
|
91
|
+
try {
|
|
92
|
+
const url = `${normalizeRegistry(registry)}/${encodePackageName(packageName)}`
|
|
93
|
+
const res = await fetchImpl(url, {
|
|
94
|
+
headers: { accept: "application/vnd.npm.install-v1+json, application/json" },
|
|
95
|
+
signal: controller.signal
|
|
96
|
+
})
|
|
97
|
+
if (!res.ok) throw new Error(`npm registry returned HTTP ${res.status}`)
|
|
98
|
+
return await res.json()
|
|
99
|
+
} finally {
|
|
100
|
+
clearTimeout(timer)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function checkForUpdate(config = {}, options = {}) {
|
|
105
|
+
const cfg = updateConfig(config)
|
|
106
|
+
if (!cfg.enabled && !options.force) return { ok: false, skipped: true, reason: "disabled" }
|
|
107
|
+
|
|
108
|
+
const now = Number(options.now ?? Date.now())
|
|
109
|
+
const stateFile = options.stateFile || updateStatePath()
|
|
110
|
+
const state = options.state ?? await readUpdateState(stateFile)
|
|
111
|
+
const intervalMs = Math.max(0, cfg.checkIntervalHours) * 60 * 60 * 1000
|
|
112
|
+
if (!options.force && intervalMs > 0 && state.checkedAt && now - Date.parse(state.checkedAt) < intervalMs) {
|
|
113
|
+
return { ok: true, skipped: true, reason: "interval", state }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const metadata = await fetchPackageMetadata({
|
|
117
|
+
packageName: options.packageName || PACKAGE_NAME,
|
|
118
|
+
registry: cfg.registry,
|
|
119
|
+
timeoutMs: cfg.timeoutMs,
|
|
120
|
+
fetchImpl: options.fetchImpl
|
|
121
|
+
})
|
|
122
|
+
const distTags = metadata["dist-tags"] || {}
|
|
123
|
+
const latestVersion = distTags[cfg.channel] || distTags.latest || metadata.version
|
|
124
|
+
const currentVersion = options.currentVersion || PACKAGE_VERSION
|
|
125
|
+
const hasUpdate = Boolean(latestVersion && compareVersions(latestVersion, currentVersion) > 0)
|
|
126
|
+
const result = {
|
|
127
|
+
ok: true,
|
|
128
|
+
packageName: options.packageName || PACKAGE_NAME,
|
|
129
|
+
channel: cfg.channel,
|
|
130
|
+
currentVersion,
|
|
131
|
+
latestVersion,
|
|
132
|
+
hasUpdate,
|
|
133
|
+
installSpec: `${options.packageName || PACKAGE_NAME}@${cfg.channel}`,
|
|
134
|
+
checkedAt: new Date(now).toISOString()
|
|
135
|
+
}
|
|
136
|
+
await writeUpdateState(result, stateFile)
|
|
137
|
+
return result
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function updateMessage(result) {
|
|
141
|
+
if (!result?.hasUpdate) return null
|
|
142
|
+
return `Update available: kkcode ${result.currentVersion} -> ${result.latestVersion} (${result.channel}). Run: kkcode update --channel ${result.channel}`
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function maybeNotifyUpdateOnStartup(config = {}, options = {}) {
|
|
146
|
+
const cfg = updateConfig(config)
|
|
147
|
+
if (!cfg.enabled || !cfg.notifyOnStartup || process.env.KKCODE_DISABLE_UPDATE_CHECK === "1") return null
|
|
148
|
+
try {
|
|
149
|
+
const result = await checkForUpdate(config, options)
|
|
150
|
+
const message = updateMessage(result)
|
|
151
|
+
if (message) {
|
|
152
|
+
const print = options.print || console.error
|
|
153
|
+
print(message)
|
|
154
|
+
if (cfg.autoInstall) {
|
|
155
|
+
const install = await installUpdate(config, { channel: result.channel, stdio: "ignore" })
|
|
156
|
+
if (install.ok) print(`kkcode update installed ${result.latestVersion}; restart kkcode to use it.`)
|
|
157
|
+
else print(`kkcode auto-update failed: ${install.error}`)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return result
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (options.verbose) (options.print || console.error)(`kkcode update check failed: ${error.message}`)
|
|
163
|
+
return { ok: false, error: error.message }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function runCommand(command, args, { cwd = process.cwd(), env = process.env, stdio = "inherit" } = {}) {
|
|
168
|
+
return new Promise((resolve) => {
|
|
169
|
+
const child = spawn(command, args, { cwd, env, stdio, shell: process.platform === "win32" })
|
|
170
|
+
child.on("exit", (code) => resolve({ ok: code === 0, code }))
|
|
171
|
+
child.on("error", (error) => resolve({ ok: false, code: 1, error: error.message }))
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function installUpdate(config = {}, options = {}) {
|
|
176
|
+
const cfg = updateConfig(config)
|
|
177
|
+
const channel = options.channel || cfg.channel || "latest"
|
|
178
|
+
const packageName = options.packageName || PACKAGE_NAME
|
|
179
|
+
const npm = options.npmCommand || process.env.npm_execpath || "npm"
|
|
180
|
+
const args = ["install", "-g", `${packageName}@${channel}`]
|
|
181
|
+
const result = await (options.runCommand || runCommand)(npm, args, options)
|
|
182
|
+
if (!result.ok) return { ok: false, code: result.code, error: result.error || `npm exited with ${result.code}` }
|
|
183
|
+
return { ok: true, command: npm, args }
|
|
184
|
+
}
|
package/src/usage/pricing.mjs
CHANGED
|
@@ -1,121 +1,122 @@
|
|
|
1
|
-
import path from "node:path"
|
|
2
|
-
import { access, readFile } from "node:fs/promises"
|
|
3
|
-
import YAML from "yaml"
|
|
4
|
-
|
|
5
|
-
const DEFAULT_PRICING = {
|
|
6
|
-
currency: "USD",
|
|
7
|
-
per_tokens: 1000000,
|
|
8
|
-
models: {
|
|
9
|
-
"claude-opus-4-6": { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 },
|
|
10
|
-
"claude-opus-4-5": { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 },
|
|
11
|
-
"claude-opus-4-1": { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 },
|
|
12
|
-
"claude-opus-4": { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 },
|
|
13
|
-
"claude-sonnet-4-6": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
|
|
14
|
-
"claude-sonnet-4-5": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
|
|
15
|
-
"claude-sonnet-4": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
|
|
16
|
-
"claude-haiku-4-5": { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 },
|
|
17
|
-
"claude-haiku-3-5": { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 },
|
|
18
|
-
"gpt-5.3-codex": { input: 15, output: 60, cache_read: 7.5, cache_write: 15 },
|
|
19
|
-
"gpt-4o": { input: 2.5, output: 10, cache_read: 1.25, cache_write: 2.5 },
|
|
20
|
-
"gpt-4o-mini": { input: 0.15, output: 0.6, cache_read: 0.075, cache_write: 0.15 },
|
|
21
|
-
"deepseek-chat": { input: 0.27, output: 1.1, cache_read: 0.07, cache_write: 0.27 },
|
|
22
|
-
"deepseek-coder": { input: 0.27, output: 1.1, cache_read: 0.07, cache_write: 0.27 },
|
|
23
|
-
"deepseek-v3.1-terminus": { input: 0.55, output: 1.65, cache_read: 0.07, cache_write: 0.55 },
|
|
24
|
-
"deepseek-v3.2": { input: 0.28, output: 0.41, cache_read: 0.03, cache_write: 0.28 },
|
|
25
|
-
"kimi-k2.5": { input: 0.55, output: 2.9, cache_read: 0, cache_write: 0 },
|
|
26
|
-
"kimi-k2-0905": { input: 0.55, output: 2.2, cache_read: 0.14, cache_write: 0 },
|
|
27
|
-
"qwen-plus": { input: 0.11, output: 1.11, cache_read: 0.02, cache_write: 0 },
|
|
28
|
-
"qwen-max": { input: 0.33, output: 1.33, cache_read: 0.07, cache_write: 0 },
|
|
29
|
-
"qwen-turbo": { input: 0.04, output: 0.08, cache_read: 0.01, cache_write: 0 },
|
|
30
|
-
"qwen3-coder-plus": { input: 0.55, output: 2.2, cache_read: 0.11, cache_write: 0 },
|
|
31
|
-
"qwen3-coder-flash": { input: 0.14, output: 0.55, cache_read: 0.03, cache_write: 0 },
|
|
32
|
-
"qwen3-coder-480b-a35b": { input: 0.83, output: 3.3, cache_read: 0.17, cache_write: 0 },
|
|
33
|
-
"doubao-seed-2.0-code": { input: 0.44, output: 2.2, cache_read: 0, cache_write: 0 },
|
|
34
|
-
"doubao-seed-2.0-pro": { input: 0.44, output: 2.2, cache_read: 0, cache_write: 0 },
|
|
35
|
-
"doubao-seed-1.8": { input: 0.11, output: 1.1, cache_read: 0, cache_write: 0 },
|
|
36
|
-
"doubao-seed-code": { input: 0.17, output: 1.1, cache_read: 0, cache_write: 0 },
|
|
37
|
-
"minimax-m2.5": { input: 0.29, output: 1.16, cache_read: 0.03, cache_write: 0 },
|
|
38
|
-
"minimax-m2.5-highspeed": { input: 0.58, output: 2.32, cache_read: 0.03, cache_write: 0 },
|
|
39
|
-
"minimax-m2.1": { input: 0.29, output: 1.16, cache_read: 0.03, cache_write: 0 },
|
|
40
|
-
"minimax-m2": { input: 0.29, output: 1.16, cache_read: 0.03, cache_write: 0 },
|
|
41
|
-
"glm-5": { input: 0.55, output: 3.05, cache_read: 0, cache_write: 0 },
|
|
42
|
-
"glm-4.7": { input: 0.41, output: 1.93, cache_read: 0, cache_write: 0 },
|
|
43
|
-
"glm-4.6": { input: 0.28, output: 1.1, cache_read: 0, cache_write: 0 }
|
|
44
|
-
},
|
|
45
|
-
default: {
|
|
46
|
-
input: 3,
|
|
47
|
-
output: 15,
|
|
48
|
-
cache_read: 0.3,
|
|
49
|
-
cache_write: 3.75
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function exists(file) {
|
|
54
|
-
try {
|
|
55
|
-
await access(file)
|
|
56
|
-
return true
|
|
57
|
-
} catch {
|
|
58
|
-
return false
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function parse(file, raw) {
|
|
63
|
-
if (file.endsWith(".json")) return JSON.parse(raw)
|
|
64
|
-
return YAML.parse(raw)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function resolvePricingPath(configState) {
|
|
68
|
-
const projectPath = configState.source.projectRaw?.usage?.pricing_file
|
|
69
|
-
if (typeof projectPath === "string" && projectPath.trim()) {
|
|
70
|
-
return path.resolve(configState.source.projectDir ?? process.cwd(), projectPath)
|
|
71
|
-
}
|
|
72
|
-
const userPath = configState.source.userRaw?.usage?.pricing_file
|
|
73
|
-
if (typeof userPath === "string" && userPath.trim()) {
|
|
74
|
-
return path.resolve(configState.source.userDir ?? process.cwd(), userPath)
|
|
75
|
-
}
|
|
76
|
-
return null
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export async function loadPricing(configState) {
|
|
80
|
-
const file = resolvePricingPath(configState)
|
|
81
|
-
if (!file || !(await exists(file))) {
|
|
82
|
-
return { pricing: DEFAULT_PRICING, source: "default", errors: [] }
|
|
83
|
-
}
|
|
84
|
-
try {
|
|
85
|
-
const raw = await readFile(file, "utf8")
|
|
86
|
-
const parsed = parse(file, raw)
|
|
87
|
-
const pricing = {
|
|
88
|
-
...DEFAULT_PRICING,
|
|
89
|
-
...parsed,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
(usage.
|
|
116
|
-
(usage.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
}
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import { access, readFile } from "node:fs/promises"
|
|
3
|
+
import YAML from "yaml"
|
|
4
|
+
|
|
5
|
+
const DEFAULT_PRICING = {
|
|
6
|
+
currency: "USD",
|
|
7
|
+
per_tokens: 1000000,
|
|
8
|
+
models: {
|
|
9
|
+
"claude-opus-4-6": { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 },
|
|
10
|
+
"claude-opus-4-5": { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 },
|
|
11
|
+
"claude-opus-4-1": { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 },
|
|
12
|
+
"claude-opus-4": { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 },
|
|
13
|
+
"claude-sonnet-4-6": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
|
|
14
|
+
"claude-sonnet-4-5": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
|
|
15
|
+
"claude-sonnet-4": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
|
|
16
|
+
"claude-haiku-4-5": { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 },
|
|
17
|
+
"claude-haiku-3-5": { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 },
|
|
18
|
+
"gpt-5.3-codex": { input: 15, output: 60, cache_read: 7.5, cache_write: 15 },
|
|
19
|
+
"gpt-4o": { input: 2.5, output: 10, cache_read: 1.25, cache_write: 2.5 },
|
|
20
|
+
"gpt-4o-mini": { input: 0.15, output: 0.6, cache_read: 0.075, cache_write: 0.15 },
|
|
21
|
+
"deepseek-chat": { input: 0.27, output: 1.1, cache_read: 0.07, cache_write: 0.27 },
|
|
22
|
+
"deepseek-coder": { input: 0.27, output: 1.1, cache_read: 0.07, cache_write: 0.27 },
|
|
23
|
+
"deepseek-v3.1-terminus": { input: 0.55, output: 1.65, cache_read: 0.07, cache_write: 0.55 },
|
|
24
|
+
"deepseek-v3.2": { input: 0.28, output: 0.41, cache_read: 0.03, cache_write: 0.28 },
|
|
25
|
+
"kimi-k2.5": { input: 0.55, output: 2.9, cache_read: 0, cache_write: 0 },
|
|
26
|
+
"kimi-k2-0905": { input: 0.55, output: 2.2, cache_read: 0.14, cache_write: 0 },
|
|
27
|
+
"qwen-plus": { input: 0.11, output: 1.11, cache_read: 0.02, cache_write: 0 },
|
|
28
|
+
"qwen-max": { input: 0.33, output: 1.33, cache_read: 0.07, cache_write: 0 },
|
|
29
|
+
"qwen-turbo": { input: 0.04, output: 0.08, cache_read: 0.01, cache_write: 0 },
|
|
30
|
+
"qwen3-coder-plus": { input: 0.55, output: 2.2, cache_read: 0.11, cache_write: 0 },
|
|
31
|
+
"qwen3-coder-flash": { input: 0.14, output: 0.55, cache_read: 0.03, cache_write: 0 },
|
|
32
|
+
"qwen3-coder-480b-a35b": { input: 0.83, output: 3.3, cache_read: 0.17, cache_write: 0 },
|
|
33
|
+
"doubao-seed-2.0-code": { input: 0.44, output: 2.2, cache_read: 0, cache_write: 0 },
|
|
34
|
+
"doubao-seed-2.0-pro": { input: 0.44, output: 2.2, cache_read: 0, cache_write: 0 },
|
|
35
|
+
"doubao-seed-1.8": { input: 0.11, output: 1.1, cache_read: 0, cache_write: 0 },
|
|
36
|
+
"doubao-seed-code": { input: 0.17, output: 1.1, cache_read: 0, cache_write: 0 },
|
|
37
|
+
"minimax-m2.5": { input: 0.29, output: 1.16, cache_read: 0.03, cache_write: 0 },
|
|
38
|
+
"minimax-m2.5-highspeed": { input: 0.58, output: 2.32, cache_read: 0.03, cache_write: 0 },
|
|
39
|
+
"minimax-m2.1": { input: 0.29, output: 1.16, cache_read: 0.03, cache_write: 0 },
|
|
40
|
+
"minimax-m2": { input: 0.29, output: 1.16, cache_read: 0.03, cache_write: 0 },
|
|
41
|
+
"glm-5": { input: 0.55, output: 3.05, cache_read: 0, cache_write: 0 },
|
|
42
|
+
"glm-4.7": { input: 0.41, output: 1.93, cache_read: 0, cache_write: 0 },
|
|
43
|
+
"glm-4.6": { input: 0.28, output: 1.1, cache_read: 0, cache_write: 0 }
|
|
44
|
+
},
|
|
45
|
+
default: {
|
|
46
|
+
input: 3,
|
|
47
|
+
output: 15,
|
|
48
|
+
cache_read: 0.3,
|
|
49
|
+
cache_write: 3.75
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function exists(file) {
|
|
54
|
+
try {
|
|
55
|
+
await access(file)
|
|
56
|
+
return true
|
|
57
|
+
} catch {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parse(file, raw) {
|
|
63
|
+
if (file.endsWith(".json")) return JSON.parse(raw)
|
|
64
|
+
return YAML.parse(raw)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolvePricingPath(configState) {
|
|
68
|
+
const projectPath = configState.source.projectRaw?.usage?.pricing_file
|
|
69
|
+
if (typeof projectPath === "string" && projectPath.trim()) {
|
|
70
|
+
return path.resolve(configState.source.projectDir ?? process.cwd(), projectPath)
|
|
71
|
+
}
|
|
72
|
+
const userPath = configState.source.userRaw?.usage?.pricing_file
|
|
73
|
+
if (typeof userPath === "string" && userPath.trim()) {
|
|
74
|
+
return path.resolve(configState.source.userDir ?? process.cwd(), userPath)
|
|
75
|
+
}
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function loadPricing(configState) {
|
|
80
|
+
const file = resolvePricingPath(configState)
|
|
81
|
+
if (!file || !(await exists(file))) {
|
|
82
|
+
return { pricing: DEFAULT_PRICING, source: "default", errors: [] }
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const raw = await readFile(file, "utf8")
|
|
86
|
+
const parsed = parse(file, raw)
|
|
87
|
+
const pricing = {
|
|
88
|
+
...DEFAULT_PRICING,
|
|
89
|
+
...parsed,
|
|
90
|
+
models: { ...DEFAULT_PRICING.models, ...(parsed.models ?? {}) },
|
|
91
|
+
default: { ...DEFAULT_PRICING.default, ...(parsed.default ?? {}) }
|
|
92
|
+
}
|
|
93
|
+
return { pricing, source: file, errors: [] }
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return { pricing: DEFAULT_PRICING, source: "default", errors: [`${file}: ${error.message}`] }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function findPricingEntry(models, model) {
|
|
100
|
+
if (models[model]) return models[model]
|
|
101
|
+
// Fuzzy: try prefix match (e.g. "claude-opus-4-6-20250601" → "claude-opus-4-6")
|
|
102
|
+
const m = String(model).toLowerCase()
|
|
103
|
+
for (const key of Object.keys(models)) {
|
|
104
|
+
if (m.startsWith(key)) return models[key]
|
|
105
|
+
}
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function calculateCost(pricing, model, usage) {
|
|
110
|
+
const entry = findPricingEntry(pricing.models, model) ?? pricing.default
|
|
111
|
+
const per = pricing.per_tokens || 1000000
|
|
112
|
+
// All providers normalize input to non-cached tokens only (see provider/*.mjs)
|
|
113
|
+
const amount =
|
|
114
|
+
((usage.input || 0) * (entry.input || 0) +
|
|
115
|
+
(usage.output || 0) * (entry.output || 0) +
|
|
116
|
+
(usage.cacheRead || 0) * (entry.cache_read || 0) +
|
|
117
|
+
(usage.cacheWrite || 0) * (entry.cache_write || 0)) /
|
|
118
|
+
per
|
|
119
|
+
const savings = ((usage.cacheRead || 0) * ((entry.input || 0) - (entry.cache_read || 0))) / per
|
|
120
|
+
const unknown = !findPricingEntry(pricing.models, model)
|
|
121
|
+
return { amount, savings, unknown, currency: pricing.currency }
|
|
122
|
+
}
|