cerebras-cli 1.0.5 → 1.0.138
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/AGENTS.md +27 -0
- package/Dockerfile +10 -0
- package/README.md +5 -3
- package/bin/{opencode.cjs → opencode} +4 -4
- package/bunfig.toml +4 -0
- package/package.json +89 -32
- package/parsers-config.ts +239 -0
- package/script/build.ts +151 -0
- package/script/postinstall.mjs +122 -0
- package/script/publish.ts +256 -0
- package/script/schema.ts +47 -0
- package/snake_game.py +111 -0
- package/src/acp/README.md +164 -0
- package/src/acp/agent.ts +812 -0
- package/src/acp/session.ts +70 -0
- package/src/acp/types.ts +22 -0
- package/src/agent/agent.ts +310 -0
- package/src/agent/generate.txt +75 -0
- package/src/auth/index.ts +70 -0
- package/src/bun/index.ts +152 -0
- package/src/bus/global.ts +10 -0
- package/src/bus/index.ts +142 -0
- package/src/cli/bootstrap.ts +17 -0
- package/src/cli/cmd/acp.ts +88 -0
- package/src/cli/cmd/agent.ts +165 -0
- package/src/cli/cmd/auth.ts +369 -0
- package/src/cli/cmd/cmd.ts +7 -0
- package/src/cli/cmd/debug/config.ts +15 -0
- package/src/cli/cmd/debug/file.ts +91 -0
- package/src/cli/cmd/debug/index.ts +43 -0
- package/src/cli/cmd/debug/lsp.ts +47 -0
- package/src/cli/cmd/debug/ripgrep.ts +83 -0
- package/src/cli/cmd/debug/scrap.ts +15 -0
- package/src/cli/cmd/debug/skill.ts +36 -0
- package/src/cli/cmd/debug/snapshot.ts +48 -0
- package/src/cli/cmd/export.ts +88 -0
- package/src/cli/cmd/generate.ts +38 -0
- package/src/cli/cmd/github.ts +1200 -0
- package/src/cli/cmd/import.ts +98 -0
- package/src/cli/cmd/mcp.ts +400 -0
- package/src/cli/cmd/models.ts +77 -0
- package/src/cli/cmd/pr.ts +112 -0
- package/src/cli/cmd/run.ts +342 -0
- package/src/cli/cmd/serve.ts +31 -0
- package/src/cli/cmd/session.ts +106 -0
- package/src/cli/cmd/stats.ts +298 -0
- package/src/cli/cmd/tui/app.tsx +833 -0
- package/src/cli/cmd/tui/attach.ts +25 -0
- package/src/cli/cmd/tui/component/border.tsx +21 -0
- package/src/cli/cmd/tui/component/cerebras-onboarding.tsx +225 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
- package/src/cli/cmd/tui/component/dialog-feedback.tsx +160 -0
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-model.tsx +223 -0
- package/src/cli/cmd/tui/component/dialog-notification.tsx +78 -0
- package/src/cli/cmd/tui/component/dialog-provider.tsx +222 -0
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +97 -0
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-status.tsx +114 -0
- package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
- package/src/cli/cmd/tui/component/logo.tsx +43 -0
- package/src/cli/cmd/tui/component/notification-banner.tsx +58 -0
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +530 -0
- package/src/cli/cmd/tui/component/prompt/history.tsx +107 -0
- package/src/cli/cmd/tui/component/prompt/index.tsx +931 -0
- package/src/cli/cmd/tui/component/quickstart-onboarding.tsx +116 -0
- package/src/cli/cmd/tui/context/args.tsx +14 -0
- package/src/cli/cmd/tui/context/directory.ts +12 -0
- package/src/cli/cmd/tui/context/exit.tsx +23 -0
- package/src/cli/cmd/tui/context/helper.tsx +25 -0
- package/src/cli/cmd/tui/context/keybind.tsx +111 -0
- package/src/cli/cmd/tui/context/kv.tsx +49 -0
- package/src/cli/cmd/tui/context/local.tsx +338 -0
- package/src/cli/cmd/tui/context/prompt.tsx +18 -0
- package/src/cli/cmd/tui/context/route.tsx +45 -0
- package/src/cli/cmd/tui/context/sdk.tsx +75 -0
- package/src/cli/cmd/tui/context/sync.tsx +374 -0
- package/src/cli/cmd/tui/context/theme/aura.json +69 -0
- package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
- package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
- package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
- package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
- package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
- package/src/cli/cmd/tui/context/theme/github.json +233 -0
- package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
- package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
- package/src/cli/cmd/tui/context/theme/material.json +235 -0
- package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
- package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
- package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
- package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
- package/src/cli/cmd/tui/context/theme/nord.json +223 -0
- package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
- package/src/cli/cmd/tui/context/theme/orng.json +245 -0
- package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
- package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
- package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
- package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
- package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
- package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
- package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
- package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
- package/src/cli/cmd/tui/context/theme.tsx +1077 -0
- package/src/cli/cmd/tui/event.ts +39 -0
- package/src/cli/cmd/tui/routes/home.tsx +150 -0
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +93 -0
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +37 -0
- package/src/cli/cmd/tui/routes/session/footer.tsx +76 -0
- package/src/cli/cmd/tui/routes/session/header.tsx +181 -0
- package/src/cli/cmd/tui/routes/session/index.tsx +1695 -0
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +686 -0
- package/src/cli/cmd/tui/spawn.ts +60 -0
- package/src/cli/cmd/tui/thread.ts +120 -0
- package/src/cli/cmd/tui/ui/dialog-alert.tsx +55 -0
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +81 -0
- package/src/cli/cmd/tui/ui/dialog-help.tsx +36 -0
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +75 -0
- package/src/cli/cmd/tui/ui/dialog-select.tsx +317 -0
- package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
- package/src/cli/cmd/tui/ui/spinner.ts +368 -0
- package/src/cli/cmd/tui/ui/toast.tsx +100 -0
- package/src/cli/cmd/tui/util/clipboard.ts +127 -0
- package/src/cli/cmd/tui/util/editor.ts +32 -0
- package/src/cli/cmd/tui/util/terminal.ts +114 -0
- package/src/cli/cmd/tui/worker.ts +63 -0
- package/src/cli/cmd/uninstall.ts +344 -0
- package/src/cli/cmd/upgrade.ts +67 -0
- package/src/cli/cmd/web.ts +84 -0
- package/src/cli/error.ts +55 -0
- package/src/cli/ui.ts +84 -0
- package/src/cli/upgrade.ts +25 -0
- package/src/command/index.ts +79 -0
- package/src/command/template/initialize.txt +10 -0
- package/src/command/template/review.txt +73 -0
- package/src/config/config.ts +886 -0
- package/src/config/markdown.ts +41 -0
- package/src/env/index.ts +26 -0
- package/src/file/fzf.ts +124 -0
- package/src/file/ignore.ts +83 -0
- package/src/file/index.ts +326 -0
- package/src/file/ripgrep.ts +391 -0
- package/src/file/time.ts +38 -0
- package/src/file/watcher.ts +89 -0
- package/src/flag/flag.ts +29 -0
- package/src/format/formatter.ts +277 -0
- package/src/format/index.ts +137 -0
- package/src/global/index.ts +52 -0
- package/src/id/id.ts +73 -0
- package/src/ide/index.ts +75 -0
- package/src/index.ts +158 -0
- package/src/installation/index.ts +194 -0
- package/src/lsp/client.ts +215 -0
- package/src/lsp/index.ts +370 -0
- package/src/lsp/language.ts +111 -0
- package/src/lsp/server.ts +1327 -0
- package/src/mcp/auth.ts +82 -0
- package/src/mcp/index.ts +576 -0
- package/src/mcp/oauth-callback.ts +203 -0
- package/src/mcp/oauth-provider.ts +132 -0
- package/src/notification/index.ts +101 -0
- package/src/patch/index.ts +622 -0
- package/src/permission/index.ts +198 -0
- package/src/plugin/index.ts +95 -0
- package/src/project/bootstrap.ts +31 -0
- package/src/project/instance.ts +68 -0
- package/src/project/project.ts +133 -0
- package/src/project/state.ts +65 -0
- package/src/project/vcs.ts +77 -0
- package/src/provider/auth.ts +143 -0
- package/src/provider/models-macro.ts +11 -0
- package/src/provider/models.ts +93 -0
- package/src/provider/provider.ts +1005 -0
- package/src/provider/sdk/openai-compatible/src/README.md +5 -0
- package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
- package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
- package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
- package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +27 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
- package/src/provider/transform.ts +406 -0
- package/src/pty/index.ts +226 -0
- package/src/ratelimit/index.ts +185 -0
- package/src/server/error.ts +36 -0
- package/src/server/project.ts +50 -0
- package/src/server/server.ts +2463 -0
- package/src/server/tui.ts +71 -0
- package/src/session/compaction.ts +257 -0
- package/src/session/index.ts +470 -0
- package/src/session/message-v2.ts +641 -0
- package/src/session/message.ts +189 -0
- package/src/session/processor.ts +448 -0
- package/src/session/prompt/anthropic-20250930.txt +166 -0
- package/src/session/prompt/anthropic.txt +105 -0
- package/src/session/prompt/anthropic_spoof.txt +1 -0
- package/src/session/prompt/beast.txt +147 -0
- package/src/session/prompt/build-switch.txt +5 -0
- package/src/session/prompt/codex.txt +318 -0
- package/src/session/prompt/compaction.txt +12 -0
- package/src/session/prompt/copilot-gpt-5.txt +143 -0
- package/src/session/prompt/gemini.txt +155 -0
- package/src/session/prompt/max-steps.txt +16 -0
- package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
- package/src/session/prompt/plan.txt +26 -0
- package/src/session/prompt/polaris.txt +107 -0
- package/src/session/prompt/qwen.txt +109 -0
- package/src/session/prompt/summarize.txt +4 -0
- package/src/session/prompt/title.txt +36 -0
- package/src/session/prompt.ts +1541 -0
- package/src/session/retry.ts +82 -0
- package/src/session/revert.ts +108 -0
- package/src/session/status.ts +75 -0
- package/src/session/summary.ts +203 -0
- package/src/session/system.ts +148 -0
- package/src/session/todo.ts +36 -0
- package/src/share/share-next.ts +195 -0
- package/src/share/share.ts +87 -0
- package/src/skill/index.ts +2 -0
- package/src/skill/skill.ts +138 -0
- package/src/snapshot/index.ts +197 -0
- package/src/storage/storage.ts +226 -0
- package/src/telemetry/index.ts +247 -0
- package/src/tool/bash.ts +365 -0
- package/src/tool/bash.txt +128 -0
- package/src/tool/batch.ts +173 -0
- package/src/tool/batch.txt +28 -0
- package/src/tool/codesearch.ts +138 -0
- package/src/tool/codesearch.txt +12 -0
- package/src/tool/edit.ts +674 -0
- package/src/tool/edit.txt +10 -0
- package/src/tool/glob.ts +65 -0
- package/src/tool/glob.txt +6 -0
- package/src/tool/grep.ts +120 -0
- package/src/tool/grep.txt +8 -0
- package/src/tool/invalid.ts +17 -0
- package/src/tool/ls.ts +110 -0
- package/src/tool/ls.txt +1 -0
- package/src/tool/lsp-diagnostics.ts +26 -0
- package/src/tool/lsp-diagnostics.txt +1 -0
- package/src/tool/lsp-hover.ts +31 -0
- package/src/tool/lsp-hover.txt +1 -0
- package/src/tool/multiedit.ts +46 -0
- package/src/tool/multiedit.txt +41 -0
- package/src/tool/patch.ts +233 -0
- package/src/tool/patch.txt +1 -0
- package/src/tool/read.ts +217 -0
- package/src/tool/read.txt +12 -0
- package/src/tool/registry.ts +150 -0
- package/src/tool/skill.ts +85 -0
- package/src/tool/task.ts +135 -0
- package/src/tool/task.txt +60 -0
- package/src/tool/todo.ts +39 -0
- package/src/tool/todoread.txt +14 -0
- package/src/tool/todowrite.txt +167 -0
- package/src/tool/tool.ts +66 -0
- package/src/tool/webfetch.ts +187 -0
- package/src/tool/webfetch.txt +14 -0
- package/src/tool/websearch.ts +150 -0
- package/src/tool/websearch.txt +11 -0
- package/src/tool/write.ts +99 -0
- package/src/tool/write.txt +8 -0
- package/src/types/shims.d.ts +3 -0
- package/src/util/color.ts +19 -0
- package/src/util/context.ts +25 -0
- package/src/util/defer.ts +12 -0
- package/src/util/eventloop.ts +20 -0
- package/src/util/filesystem.ts +69 -0
- package/src/util/fn.ts +11 -0
- package/src/util/iife.ts +3 -0
- package/src/util/keybind.ts +79 -0
- package/src/util/lazy.ts +11 -0
- package/src/util/locale.ts +81 -0
- package/src/util/lock.ts +98 -0
- package/src/util/log.ts +177 -0
- package/src/util/queue.ts +32 -0
- package/src/util/rpc.ts +42 -0
- package/src/util/scrap.ts +10 -0
- package/src/util/signal.ts +12 -0
- package/src/util/timeout.ts +14 -0
- package/src/util/token.ts +7 -0
- package/src/util/wildcard.ts +54 -0
- package/sst-env.d.ts +9 -0
- package/test/bun.test.ts +53 -0
- package/test/config/agent-color.test.ts +66 -0
- package/test/config/config.test.ts +503 -0
- package/test/config/markdown.test.ts +89 -0
- package/test/file/ignore.test.ts +10 -0
- package/test/fixture/fixture.ts +28 -0
- package/test/fixture/lsp/fake-lsp-server.js +77 -0
- package/test/ide/ide.test.ts +82 -0
- package/test/keybind.test.ts +317 -0
- package/test/lsp/client.test.ts +95 -0
- package/test/patch/patch.test.ts +348 -0
- package/test/preload.ts +38 -0
- package/test/project/project.test.ts +42 -0
- package/test/provider/provider.test.ts +1809 -0
- package/test/provider/transform.test.ts +305 -0
- package/test/session/retry.test.ts +61 -0
- package/test/session/session.test.ts +71 -0
- package/test/snapshot/snapshot.test.ts +939 -0
- package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
- package/test/tool/bash.test.ts +55 -0
- package/test/tool/patch.test.ts +259 -0
- package/test/util/iife.test.ts +36 -0
- package/test/util/lazy.test.ts +50 -0
- package/test/util/timeout.test.ts +21 -0
- package/test/util/wildcard.test.ts +55 -0
- package/tsconfig.json +17 -0
- package/cerebras-cli-1.0.0.tgz +0 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { Log } from "../util/log"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import fs from "fs/promises"
|
|
4
|
+
import { Global } from "../global"
|
|
5
|
+
import { lazy } from "../util/lazy"
|
|
6
|
+
import { Lock } from "../util/lock"
|
|
7
|
+
import { $ } from "bun"
|
|
8
|
+
import { NamedError } from "@opencode-ai/util/error"
|
|
9
|
+
import z from "zod"
|
|
10
|
+
|
|
11
|
+
export namespace Storage {
|
|
12
|
+
const log = Log.create({ service: "storage" })
|
|
13
|
+
|
|
14
|
+
type Migration = (dir: string) => Promise<void>
|
|
15
|
+
|
|
16
|
+
export const NotFoundError = NamedError.create(
|
|
17
|
+
"NotFoundError",
|
|
18
|
+
z.object({
|
|
19
|
+
message: z.string(),
|
|
20
|
+
}),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
const MIGRATIONS: Migration[] = [
|
|
24
|
+
async (dir) => {
|
|
25
|
+
const project = path.resolve(dir, "../project")
|
|
26
|
+
if (!fs.exists(project)) return
|
|
27
|
+
for await (const projectDir of new Bun.Glob("*").scan({
|
|
28
|
+
cwd: project,
|
|
29
|
+
onlyFiles: false,
|
|
30
|
+
})) {
|
|
31
|
+
log.info(`migrating project ${projectDir}`)
|
|
32
|
+
let projectID = projectDir
|
|
33
|
+
const fullProjectDir = path.join(project, projectDir)
|
|
34
|
+
let worktree = "/"
|
|
35
|
+
|
|
36
|
+
if (projectID !== "global") {
|
|
37
|
+
for await (const msgFile of new Bun.Glob("storage/session/message/*/*.json").scan({
|
|
38
|
+
cwd: path.join(project, projectDir),
|
|
39
|
+
absolute: true,
|
|
40
|
+
})) {
|
|
41
|
+
const json = await Bun.file(msgFile).json()
|
|
42
|
+
worktree = json.path?.root
|
|
43
|
+
if (worktree) break
|
|
44
|
+
}
|
|
45
|
+
if (!worktree) continue
|
|
46
|
+
if (!(await fs.exists(worktree))) continue
|
|
47
|
+
const [id] = await $`git rev-list --max-parents=0 --all`
|
|
48
|
+
.quiet()
|
|
49
|
+
.nothrow()
|
|
50
|
+
.cwd(worktree)
|
|
51
|
+
.text()
|
|
52
|
+
.then((x) =>
|
|
53
|
+
x
|
|
54
|
+
.split("\n")
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
.map((x) => x.trim())
|
|
57
|
+
.toSorted(),
|
|
58
|
+
)
|
|
59
|
+
if (!id) continue
|
|
60
|
+
projectID = id
|
|
61
|
+
|
|
62
|
+
await Bun.write(
|
|
63
|
+
path.join(dir, "project", projectID + ".json"),
|
|
64
|
+
JSON.stringify({
|
|
65
|
+
id,
|
|
66
|
+
vcs: "git",
|
|
67
|
+
worktree,
|
|
68
|
+
time: {
|
|
69
|
+
created: Date.now(),
|
|
70
|
+
initialized: Date.now(),
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
log.info(`migrating sessions for project ${projectID}`)
|
|
76
|
+
for await (const sessionFile of new Bun.Glob("storage/session/info/*.json").scan({
|
|
77
|
+
cwd: fullProjectDir,
|
|
78
|
+
absolute: true,
|
|
79
|
+
})) {
|
|
80
|
+
const dest = path.join(dir, "session", projectID, path.basename(sessionFile))
|
|
81
|
+
log.info("copying", {
|
|
82
|
+
sessionFile,
|
|
83
|
+
dest,
|
|
84
|
+
})
|
|
85
|
+
const session = await Bun.file(sessionFile).json()
|
|
86
|
+
await Bun.write(dest, JSON.stringify(session))
|
|
87
|
+
log.info(`migrating messages for session ${session.id}`)
|
|
88
|
+
for await (const msgFile of new Bun.Glob(`storage/session/message/${session.id}/*.json`).scan({
|
|
89
|
+
cwd: fullProjectDir,
|
|
90
|
+
absolute: true,
|
|
91
|
+
})) {
|
|
92
|
+
const dest = path.join(dir, "message", session.id, path.basename(msgFile))
|
|
93
|
+
log.info("copying", {
|
|
94
|
+
msgFile,
|
|
95
|
+
dest,
|
|
96
|
+
})
|
|
97
|
+
const message = await Bun.file(msgFile).json()
|
|
98
|
+
await Bun.write(dest, JSON.stringify(message))
|
|
99
|
+
|
|
100
|
+
log.info(`migrating parts for message ${message.id}`)
|
|
101
|
+
for await (const partFile of new Bun.Glob(`storage/session/part/${session.id}/${message.id}/*.json`).scan(
|
|
102
|
+
{
|
|
103
|
+
cwd: fullProjectDir,
|
|
104
|
+
absolute: true,
|
|
105
|
+
},
|
|
106
|
+
)) {
|
|
107
|
+
const dest = path.join(dir, "part", message.id, path.basename(partFile))
|
|
108
|
+
const part = await Bun.file(partFile).json()
|
|
109
|
+
log.info("copying", {
|
|
110
|
+
partFile,
|
|
111
|
+
dest,
|
|
112
|
+
})
|
|
113
|
+
await Bun.write(dest, JSON.stringify(part))
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
async (dir) => {
|
|
121
|
+
for await (const item of new Bun.Glob("session/*/*.json").scan({
|
|
122
|
+
cwd: dir,
|
|
123
|
+
absolute: true,
|
|
124
|
+
})) {
|
|
125
|
+
const session = await Bun.file(item).json()
|
|
126
|
+
if (!session.projectID) continue
|
|
127
|
+
if (!session.summary?.diffs) continue
|
|
128
|
+
const { diffs } = session.summary
|
|
129
|
+
await Bun.file(path.join(dir, "session_diff", session.id + ".json")).write(JSON.stringify(diffs))
|
|
130
|
+
await Bun.file(path.join(dir, "session", session.projectID, session.id + ".json")).write(
|
|
131
|
+
JSON.stringify({
|
|
132
|
+
...session,
|
|
133
|
+
summary: {
|
|
134
|
+
additions: diffs.reduce((sum: any, x: any) => sum + x.additions, 0),
|
|
135
|
+
deletions: diffs.reduce((sum: any, x: any) => sum + x.deletions, 0),
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
const state = lazy(async () => {
|
|
144
|
+
const dir = path.join(Global.Path.data, "storage")
|
|
145
|
+
const migration = await Bun.file(path.join(dir, "migration"))
|
|
146
|
+
.json()
|
|
147
|
+
.then((x) => parseInt(x))
|
|
148
|
+
.catch(() => 0)
|
|
149
|
+
for (let index = migration; index < MIGRATIONS.length; index++) {
|
|
150
|
+
log.info("running migration", { index })
|
|
151
|
+
const migration = MIGRATIONS[index]
|
|
152
|
+
await migration(dir).catch(() => log.error("failed to run migration", { index }))
|
|
153
|
+
await Bun.write(path.join(dir, "migration"), (index + 1).toString())
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
dir,
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
export async function remove(key: string[]) {
|
|
161
|
+
const dir = await state().then((x) => x.dir)
|
|
162
|
+
const target = path.join(dir, ...key) + ".json"
|
|
163
|
+
return withErrorHandling(async () => {
|
|
164
|
+
await fs.unlink(target).catch(() => {})
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function read<T>(key: string[]) {
|
|
169
|
+
const dir = await state().then((x) => x.dir)
|
|
170
|
+
const target = path.join(dir, ...key) + ".json"
|
|
171
|
+
return withErrorHandling(async () => {
|
|
172
|
+
using _ = await Lock.read(target)
|
|
173
|
+
const result = await Bun.file(target).json()
|
|
174
|
+
return result as T
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function update<T>(key: string[], fn: (draft: T) => void) {
|
|
179
|
+
const dir = await state().then((x) => x.dir)
|
|
180
|
+
const target = path.join(dir, ...key) + ".json"
|
|
181
|
+
return withErrorHandling(async () => {
|
|
182
|
+
using _ = await Lock.write(target)
|
|
183
|
+
const content = await Bun.file(target).json()
|
|
184
|
+
fn(content)
|
|
185
|
+
await Bun.write(target, JSON.stringify(content, null, 2))
|
|
186
|
+
return content as T
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function write<T>(key: string[], content: T) {
|
|
191
|
+
const dir = await state().then((x) => x.dir)
|
|
192
|
+
const target = path.join(dir, ...key) + ".json"
|
|
193
|
+
return withErrorHandling(async () => {
|
|
194
|
+
using _ = await Lock.write(target)
|
|
195
|
+
await Bun.write(target, JSON.stringify(content, null, 2))
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function withErrorHandling<T>(body: () => Promise<T>) {
|
|
200
|
+
return body().catch((e) => {
|
|
201
|
+
if (!(e instanceof Error)) throw e
|
|
202
|
+
const errnoException = e as NodeJS.ErrnoException
|
|
203
|
+
if (errnoException.code === "ENOENT") {
|
|
204
|
+
throw new NotFoundError({ message: `Resource not found: ${errnoException.path}` })
|
|
205
|
+
}
|
|
206
|
+
throw e
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const glob = new Bun.Glob("**/*")
|
|
211
|
+
export async function list(prefix: string[]) {
|
|
212
|
+
const dir = await state().then((x) => x.dir)
|
|
213
|
+
try {
|
|
214
|
+
const result = await Array.fromAsync(
|
|
215
|
+
glob.scan({
|
|
216
|
+
cwd: path.join(dir, ...prefix),
|
|
217
|
+
onlyFiles: true,
|
|
218
|
+
}),
|
|
219
|
+
).then((results) => results.map((x) => [...prefix, ...x.slice(0, -5).split(path.sep)]))
|
|
220
|
+
result.sort()
|
|
221
|
+
return result
|
|
222
|
+
} catch {
|
|
223
|
+
return []
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { Log } from "@/util/log"
|
|
2
|
+
import { Instance } from "@/project/instance"
|
|
3
|
+
import z from "zod"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Telemetry module for logging API request metrics to Cloudflare worker
|
|
7
|
+
*
|
|
8
|
+
* Tracks cache hit rates, token usage, conversation turns, and other metrics
|
|
9
|
+
* for monitoring and optimization purposes.
|
|
10
|
+
*/
|
|
11
|
+
export namespace Telemetry {
|
|
12
|
+
const log = Log.create({ service: "telemetry" })
|
|
13
|
+
|
|
14
|
+
// Telemetry endpoint
|
|
15
|
+
const TELEMETRY_ENDPOINT =
|
|
16
|
+
process.env.OPENCODE_TELEMETRY_URL || "https://opencode-telemetry.kevin-taylor-d8d.workers.dev"
|
|
17
|
+
|
|
18
|
+
// Batch settings
|
|
19
|
+
const BATCH_SIZE = 10
|
|
20
|
+
const BATCH_INTERVAL_MS = 30_000 // 30 seconds
|
|
21
|
+
|
|
22
|
+
// Schema for telemetry entry
|
|
23
|
+
export const Entry = z.object({
|
|
24
|
+
// Identifiers
|
|
25
|
+
sessionID: z.string().optional(),
|
|
26
|
+
providerID: z.string().optional(),
|
|
27
|
+
modelID: z.string().optional(),
|
|
28
|
+
apiKeyHash: z.string().optional(), // Hash of API key for identification
|
|
29
|
+
|
|
30
|
+
// Per-step token metrics
|
|
31
|
+
inputTokens: z.number().default(0),
|
|
32
|
+
outputTokens: z.number().default(0),
|
|
33
|
+
reasoningTokens: z.number().default(0),
|
|
34
|
+
cachedTokens: z.number().default(0),
|
|
35
|
+
|
|
36
|
+
// Per-step computed metrics
|
|
37
|
+
cacheHitRate: z.number().default(0),
|
|
38
|
+
|
|
39
|
+
// Session-level cumulative totals (like sidebar displays)
|
|
40
|
+
sessionTotalCachedTokens: z.number().default(0),
|
|
41
|
+
sessionTotalPromptTokens: z.number().default(0),
|
|
42
|
+
sessionTotalOutputTokens: z.number().default(0),
|
|
43
|
+
sessionOverallHitRate: z.number().default(0),
|
|
44
|
+
|
|
45
|
+
// Session context
|
|
46
|
+
conversationTurns: z.number().default(0),
|
|
47
|
+
|
|
48
|
+
// Metadata
|
|
49
|
+
finishReason: z.string().optional(),
|
|
50
|
+
})
|
|
51
|
+
export type Entry = z.infer<typeof Entry>
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Hash an API key for telemetry (privacy-preserving identifier)
|
|
55
|
+
* Uses last 8 chars + hash to create a unique but non-reversible ID
|
|
56
|
+
*/
|
|
57
|
+
export async function hashApiKey(apiKey: string | undefined): Promise<string | undefined> {
|
|
58
|
+
if (!apiKey || apiKey.length < 8) return undefined
|
|
59
|
+
// Take last 4 chars (visible part) + hash of full key
|
|
60
|
+
const suffix = apiKey.slice(-4)
|
|
61
|
+
const hash = Bun.hash(apiKey).toString(16).slice(0, 8)
|
|
62
|
+
return `${hash}_${suffix}`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Internal state for batching
|
|
66
|
+
const state = Instance.state(
|
|
67
|
+
() => {
|
|
68
|
+
const queue: Entry[] = []
|
|
69
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
queue,
|
|
73
|
+
timer,
|
|
74
|
+
enabled: true,
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
async (entry) => {
|
|
78
|
+
// Flush remaining entries on shutdown
|
|
79
|
+
if (entry.timer) {
|
|
80
|
+
clearTimeout(entry.timer)
|
|
81
|
+
}
|
|
82
|
+
if (entry.queue.length > 0) {
|
|
83
|
+
await flush(entry.queue)
|
|
84
|
+
entry.queue.length = 0
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Check if telemetry is enabled
|
|
91
|
+
*/
|
|
92
|
+
export function isEnabled(): boolean {
|
|
93
|
+
return state().enabled
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Enable or disable telemetry
|
|
98
|
+
*/
|
|
99
|
+
export function setEnabled(enabled: boolean): void {
|
|
100
|
+
state().enabled = enabled
|
|
101
|
+
if (!enabled) {
|
|
102
|
+
// Clear any pending entries
|
|
103
|
+
state().queue.length = 0
|
|
104
|
+
if (state().timer) {
|
|
105
|
+
clearTimeout(state().timer)
|
|
106
|
+
state().timer = undefined
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Log a telemetry entry
|
|
113
|
+
*
|
|
114
|
+
* Entries are batched and sent periodically to reduce network overhead.
|
|
115
|
+
*/
|
|
116
|
+
export function track(entry: Entry): void {
|
|
117
|
+
const s = state()
|
|
118
|
+
if (!s.enabled) return
|
|
119
|
+
|
|
120
|
+
log.info("tracking telemetry", {
|
|
121
|
+
providerID: entry.providerID,
|
|
122
|
+
modelID: entry.modelID,
|
|
123
|
+
cacheHitRate: entry.cacheHitRate,
|
|
124
|
+
cachedTokens: entry.cachedTokens,
|
|
125
|
+
inputTokens: entry.inputTokens,
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
s.queue.push(entry)
|
|
129
|
+
|
|
130
|
+
// Send immediately if batch is full
|
|
131
|
+
if (s.queue.length >= BATCH_SIZE) {
|
|
132
|
+
const entries = s.queue.splice(0, BATCH_SIZE)
|
|
133
|
+
flush(entries).catch((e) => log.error("flush error", { error: e }))
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Start batch timer if not already running
|
|
137
|
+
if (!s.timer) {
|
|
138
|
+
s.timer = setTimeout(() => {
|
|
139
|
+
s.timer = undefined
|
|
140
|
+
if (s.queue.length > 0) {
|
|
141
|
+
const entries = s.queue.splice(0)
|
|
142
|
+
flush(entries).catch((e) => log.error("flush error", { error: e }))
|
|
143
|
+
}
|
|
144
|
+
}, BATCH_INTERVAL_MS)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Convenience method to track from session processor data
|
|
150
|
+
*/
|
|
151
|
+
export function trackFromUsage(input: {
|
|
152
|
+
sessionID: string
|
|
153
|
+
providerID: string
|
|
154
|
+
modelID: string
|
|
155
|
+
apiKeyHash?: string // Pre-hashed API key identifier
|
|
156
|
+
// Per-step tokens
|
|
157
|
+
tokens: {
|
|
158
|
+
input: number
|
|
159
|
+
output: number
|
|
160
|
+
reasoning: number
|
|
161
|
+
cache: {
|
|
162
|
+
read: number
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
conversationTurns: number
|
|
166
|
+
finishReason?: string
|
|
167
|
+
// Session-level cumulative totals
|
|
168
|
+
sessionTotals?: {
|
|
169
|
+
cachedTokens: number
|
|
170
|
+
promptTokens: number
|
|
171
|
+
outputTokens: number
|
|
172
|
+
}
|
|
173
|
+
}): void {
|
|
174
|
+
// Calculate per-step cache hit rate
|
|
175
|
+
const totalPromptTokens = input.tokens.input + input.tokens.cache.read
|
|
176
|
+
const cacheHitRate = totalPromptTokens > 0 ? (input.tokens.cache.read / totalPromptTokens) * 100 : 0
|
|
177
|
+
|
|
178
|
+
// Calculate session-level overall hit rate
|
|
179
|
+
const sessionOverallHitRate =
|
|
180
|
+
input.sessionTotals && input.sessionTotals.promptTokens > 0
|
|
181
|
+
? (input.sessionTotals.cachedTokens / input.sessionTotals.promptTokens) * 100
|
|
182
|
+
: 0
|
|
183
|
+
|
|
184
|
+
track({
|
|
185
|
+
sessionID: input.sessionID,
|
|
186
|
+
providerID: input.providerID,
|
|
187
|
+
modelID: input.modelID,
|
|
188
|
+
apiKeyHash: input.apiKeyHash,
|
|
189
|
+
// Per-step metrics
|
|
190
|
+
inputTokens: input.tokens.input,
|
|
191
|
+
outputTokens: input.tokens.output,
|
|
192
|
+
reasoningTokens: input.tokens.reasoning,
|
|
193
|
+
cachedTokens: input.tokens.cache.read,
|
|
194
|
+
cacheHitRate,
|
|
195
|
+
// Session-level cumulative totals
|
|
196
|
+
sessionTotalCachedTokens: input.sessionTotals?.cachedTokens ?? 0,
|
|
197
|
+
sessionTotalPromptTokens: input.sessionTotals?.promptTokens ?? 0,
|
|
198
|
+
sessionTotalOutputTokens: input.sessionTotals?.outputTokens ?? 0,
|
|
199
|
+
sessionOverallHitRate,
|
|
200
|
+
// Context
|
|
201
|
+
conversationTurns: input.conversationTurns,
|
|
202
|
+
finishReason: input.finishReason,
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Flush entries to the telemetry endpoint
|
|
208
|
+
*/
|
|
209
|
+
async function flush(entries: Entry[]): Promise<void> {
|
|
210
|
+
if (entries.length === 0) return
|
|
211
|
+
|
|
212
|
+
log.info("flushing telemetry", { count: entries.length })
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const enrichedEntries = entries
|
|
216
|
+
|
|
217
|
+
const res = await fetch(`${TELEMETRY_ENDPOINT}/batch`, {
|
|
218
|
+
method: "POST",
|
|
219
|
+
headers: { "Content-Type": "application/json" },
|
|
220
|
+
body: JSON.stringify({ entries: enrichedEntries }),
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
if (!res.ok) {
|
|
224
|
+
log.error("telemetry flush failed", { status: res.status })
|
|
225
|
+
}
|
|
226
|
+
} catch (e) {
|
|
227
|
+
// Silently fail - telemetry should not disrupt normal operation
|
|
228
|
+
log.error("telemetry error", { error: e })
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Force flush any pending entries immediately
|
|
234
|
+
*/
|
|
235
|
+
export async function forceFlush(): Promise<void> {
|
|
236
|
+
const s = state()
|
|
237
|
+
if (s.timer) {
|
|
238
|
+
clearTimeout(s.timer)
|
|
239
|
+
s.timer = undefined
|
|
240
|
+
}
|
|
241
|
+
if (s.queue.length > 0) {
|
|
242
|
+
const entries = s.queue.splice(0)
|
|
243
|
+
await flush(entries)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|