codepro-ia 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +27 -0
- package/Dockerfile +18 -0
- package/README.md +70 -0
- package/bin/opencode +84 -0
- package/bunfig.toml +7 -0
- package/package.json +144 -0
- package/parsers-config.ts +253 -0
- package/script/build.ts +167 -0
- package/script/postinstall.mjs +125 -0
- package/script/publish-registries.ts +187 -0
- package/script/publish.ts +70 -0
- package/script/schema.ts +47 -0
- package/src/acp/README.md +164 -0
- package/src/acp/agent.ts +1127 -0
- package/src/acp/session.ts +101 -0
- package/src/acp/types.ts +22 -0
- package/src/agent/agent.ts +311 -0
- package/src/agent/generate.txt +75 -0
- package/src/agent/prompt/compaction.txt +12 -0
- package/src/agent/prompt/explore.txt +18 -0
- package/src/agent/prompt/summary.txt +11 -0
- package/src/agent/prompt/title.txt +43 -0
- package/src/auth/index.ts +73 -0
- package/src/bun/index.ts +134 -0
- package/src/bus/bus-event.ts +43 -0
- package/src/bus/global.ts +10 -0
- package/src/bus/index.ts +105 -0
- package/src/cli/bootstrap.ts +17 -0
- package/src/cli/cmd/acp.ts +69 -0
- package/src/cli/cmd/agent.ts +257 -0
- package/src/cli/cmd/auth.ts +400 -0
- package/src/cli/cmd/cmd.ts +7 -0
- package/src/cli/cmd/debug/agent.ts +166 -0
- package/src/cli/cmd/debug/config.ts +16 -0
- package/src/cli/cmd/debug/file.ts +97 -0
- package/src/cli/cmd/debug/index.ts +48 -0
- package/src/cli/cmd/debug/lsp.ts +52 -0
- package/src/cli/cmd/debug/ripgrep.ts +87 -0
- package/src/cli/cmd/debug/scrap.ts +16 -0
- package/src/cli/cmd/debug/skill.ts +16 -0
- package/src/cli/cmd/debug/snapshot.ts +52 -0
- package/src/cli/cmd/export.ts +88 -0
- package/src/cli/cmd/generate.ts +38 -0
- package/src/cli/cmd/github.ts +1548 -0
- package/src/cli/cmd/import.ts +98 -0
- package/src/cli/cmd/mcp.ts +755 -0
- package/src/cli/cmd/models.ts +77 -0
- package/src/cli/cmd/pr.ts +112 -0
- package/src/cli/cmd/run.ts +395 -0
- package/src/cli/cmd/serve.ts +20 -0
- package/src/cli/cmd/session.ts +135 -0
- package/src/cli/cmd/stats.ts +402 -0
- package/src/cli/cmd/tui/app.tsx +724 -0
- package/src/cli/cmd/tui/attach.ts +31 -0
- package/src/cli/cmd/tui/component/border.tsx +21 -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-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-model.tsx +234 -0
- package/src/cli/cmd/tui/component/dialog-provider.tsx +256 -0
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +114 -0
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
- package/src/cli/cmd/tui/component/dialog-status.tsx +164 -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 +106 -0
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +763 -0
- package/src/cli/cmd/tui/component/prompt/frecency.tsx +89 -0
- package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
- package/src/cli/cmd/tui/component/prompt/index.tsx +1090 -0
- package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
- package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
- package/src/cli/cmd/tui/component/tips.tsx +153 -0
- package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
- package/src/cli/cmd/tui/context/args.tsx +14 -0
- package/src/cli/cmd/tui/context/directory.ts +13 -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 +101 -0
- package/src/cli/cmd/tui/context/kv.tsx +52 -0
- package/src/cli/cmd/tui/context/local.tsx +402 -0
- package/src/cli/cmd/tui/context/prompt.tsx +18 -0
- package/src/cli/cmd/tui/context/route.tsx +46 -0
- package/src/cli/cmd/tui/context/sdk.tsx +94 -0
- package/src/cli/cmd/tui/context/sync.tsx +427 -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/carbonfox.json +248 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -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/cursor.json +249 -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/lucent-orng.json +237 -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 +249 -0
- package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -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 +1152 -0
- package/src/cli/cmd/tui/event.ts +46 -0
- package/src/cli/cmd/tui/routes/home.tsx +141 -0
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
- package/src/cli/cmd/tui/routes/session/header.tsx +136 -0
- package/src/cli/cmd/tui/routes/session/index.tsx +1936 -0
- package/src/cli/cmd/tui/routes/session/permission.tsx +489 -0
- package/src/cli/cmd/tui/routes/session/question.tsx +435 -0
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +312 -0
- package/src/cli/cmd/tui/thread.ts +165 -0
- package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
- package/src/cli/cmd/tui/ui/dialog-export-options.tsx +204 -0
- package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
- package/src/cli/cmd/tui/ui/dialog-select.tsx +354 -0
- package/src/cli/cmd/tui/ui/dialog.tsx +167 -0
- package/src/cli/cmd/tui/ui/link.tsx +28 -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 +144 -0
- package/src/cli/cmd/tui/util/editor.ts +32 -0
- package/src/cli/cmd/tui/util/signal.ts +7 -0
- package/src/cli/cmd/tui/util/terminal.ts +114 -0
- package/src/cli/cmd/tui/util/transcript.ts +98 -0
- package/src/cli/cmd/tui/worker.ts +152 -0
- package/src/cli/cmd/uninstall.ts +344 -0
- package/src/cli/cmd/upgrade.ts +73 -0
- package/src/cli/cmd/web.ts +81 -0
- package/src/cli/error.ts +57 -0
- package/src/cli/network.ts +53 -0
- package/src/cli/ui.ts +88 -0
- package/src/cli/upgrade.ts +25 -0
- package/src/command/index.ts +131 -0
- package/src/command/template/initialize.txt +10 -0
- package/src/command/template/review.txt +97 -0
- package/src/config/config.ts +1246 -0
- package/src/config/markdown.ts +93 -0
- package/src/env/index.ts +26 -0
- package/src/file/ignore.ts +83 -0
- package/src/file/index.ts +411 -0
- package/src/file/ripgrep.ts +409 -0
- package/src/file/time.ts +64 -0
- package/src/file/watcher.ts +118 -0
- package/src/flag/flag.ts +54 -0
- package/src/format/formatter.ts +359 -0
- package/src/format/index.ts +137 -0
- package/src/global/index.ts +55 -0
- package/src/id/id.ts +83 -0
- package/src/ide/index.ts +76 -0
- package/src/index.ts +159 -0
- package/src/installation/index.ts +246 -0
- package/src/lsp/client.ts +252 -0
- package/src/lsp/index.ts +485 -0
- package/src/lsp/language.ts +119 -0
- package/src/lsp/server.ts +2032 -0
- package/src/mcp/auth.ts +135 -0
- package/src/mcp/index.ts +926 -0
- package/src/mcp/oauth-callback.ts +200 -0
- package/src/mcp/oauth-provider.ts +154 -0
- package/src/patch/index.ts +622 -0
- package/src/permission/arity.ts +163 -0
- package/src/permission/index.ts +210 -0
- package/src/permission/next.ts +269 -0
- package/src/plugin/codex.ts +493 -0
- package/src/plugin/copilot.ts +269 -0
- package/src/plugin/index.ts +135 -0
- package/src/project/bootstrap.ts +31 -0
- package/src/project/instance.ts +91 -0
- package/src/project/project.ts +320 -0
- package/src/project/state.ts +66 -0
- package/src/project/vcs.ts +76 -0
- package/src/provider/auth.ts +147 -0
- package/src/provider/models-macro.ts +11 -0
- package/src/provider/models.ts +112 -0
- package/src/provider/provider.ts +1220 -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 +22 -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 +705 -0
- package/src/pty/index.ts +229 -0
- package/src/question/index.ts +171 -0
- package/src/server/error.ts +36 -0
- package/src/server/mdns.ts +59 -0
- package/src/server/routes/config.ts +92 -0
- package/src/server/routes/experimental.ts +157 -0
- package/src/server/routes/file.ts +197 -0
- package/src/server/routes/global.ts +135 -0
- package/src/server/routes/mcp.ts +225 -0
- package/src/server/routes/permission.ts +68 -0
- package/src/server/routes/project.ts +82 -0
- package/src/server/routes/provider.ts +165 -0
- package/src/server/routes/pty.ts +169 -0
- package/src/server/routes/question.ts +98 -0
- package/src/server/routes/session.ts +935 -0
- package/src/server/routes/tui.ts +377 -0
- package/src/server/server.ts +578 -0
- package/src/session/compaction.ts +225 -0
- package/src/session/index.ts +488 -0
- package/src/session/llm.ts +279 -0
- package/src/session/message-v2.ts +702 -0
- package/src/session/message.ts +189 -0
- package/src/session/processor.ts +406 -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 +72 -0
- package/src/session/prompt/codex_header.txt +72 -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/qwen.txt +109 -0
- package/src/session/prompt.ts +1792 -0
- package/src/session/retry.ts +90 -0
- package/src/session/revert.ts +108 -0
- package/src/session/status.ts +76 -0
- package/src/session/summary.ts +150 -0
- package/src/session/system.ts +138 -0
- package/src/session/todo.ts +37 -0
- package/src/share/share-next.ts +194 -0
- package/src/share/share.ts +87 -0
- package/src/shell/shell.ts +67 -0
- package/src/skill/index.ts +1 -0
- package/src/skill/skill.ts +136 -0
- package/src/snapshot/index.ts +199 -0
- package/src/storage/storage.ts +227 -0
- package/src/tool/bash.ts +258 -0
- package/src/tool/bash.txt +115 -0
- package/src/tool/batch.ts +175 -0
- package/src/tool/batch.txt +24 -0
- package/src/tool/codesearch.ts +132 -0
- package/src/tool/codesearch.txt +12 -0
- package/src/tool/edit.ts +645 -0
- package/src/tool/edit.txt +10 -0
- package/src/tool/external-directory.ts +32 -0
- package/src/tool/glob.ts +77 -0
- package/src/tool/glob.txt +6 -0
- package/src/tool/grep.ts +154 -0
- package/src/tool/grep.txt +8 -0
- package/src/tool/invalid.ts +17 -0
- package/src/tool/ls.ts +121 -0
- package/src/tool/ls.txt +1 -0
- package/src/tool/lsp.ts +96 -0
- package/src/tool/lsp.txt +19 -0
- package/src/tool/multiedit.ts +46 -0
- package/src/tool/multiedit.txt +41 -0
- package/src/tool/patch.ts +201 -0
- package/src/tool/patch.txt +1 -0
- package/src/tool/plan-enter.txt +14 -0
- package/src/tool/plan-exit.txt +13 -0
- package/src/tool/plan.ts +130 -0
- package/src/tool/question.ts +33 -0
- package/src/tool/question.txt +10 -0
- package/src/tool/read.ts +200 -0
- package/src/tool/read.txt +12 -0
- package/src/tool/registry.ts +143 -0
- package/src/tool/skill.ts +75 -0
- package/src/tool/task.ts +188 -0
- package/src/tool/task.txt +60 -0
- package/src/tool/todo.ts +53 -0
- package/src/tool/todoread.txt +14 -0
- package/src/tool/todowrite.txt +167 -0
- package/src/tool/tool.ts +88 -0
- package/src/tool/truncation.ts +99 -0
- package/src/tool/webfetch.ts +182 -0
- package/src/tool/webfetch.txt +13 -0
- package/src/tool/websearch.ts +150 -0
- package/src/tool/websearch.txt +14 -0
- package/src/tool/write.ts +80 -0
- package/src/tool/write.txt +8 -0
- package/src/util/archive.ts +16 -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 +93 -0
- package/src/util/fn.ts +11 -0
- package/src/util/format.ts +20 -0
- package/src/util/iife.ts +3 -0
- package/src/util/keybind.ts +103 -0
- package/src/util/lazy.ts +18 -0
- package/src/util/locale.ts +81 -0
- package/src/util/lock.ts +98 -0
- package/src/util/log.ts +180 -0
- package/src/util/queue.ts +32 -0
- package/src/util/rpc.ts +66 -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 +56 -0
- package/src/worktree/index.ts +217 -0
- package/sst-env.d.ts +9 -0
- package/test/agent/agent.test.ts +638 -0
- package/test/bun.test.ts +53 -0
- package/test/cli/github-action.test.ts +129 -0
- package/test/cli/github-remote.test.ts +80 -0
- package/test/cli/tui/transcript.test.ts +297 -0
- package/test/config/agent-color.test.ts +66 -0
- package/test/config/config.test.ts +1376 -0
- package/test/config/fixtures/empty-frontmatter.md +4 -0
- package/test/config/fixtures/frontmatter.md +28 -0
- package/test/config/fixtures/no-frontmatter.md +1 -0
- package/test/config/markdown.test.ts +192 -0
- package/test/file/ignore.test.ts +10 -0
- package/test/file/path-traversal.test.ts +198 -0
- package/test/fixture/fixture.ts +45 -0
- package/test/fixture/lsp/fake-lsp-server.js +77 -0
- package/test/ide/ide.test.ts +82 -0
- package/test/keybind.test.ts +421 -0
- package/test/lsp/client.test.ts +95 -0
- package/test/mcp/headers.test.ts +153 -0
- package/test/mcp/oauth-browser.test.ts +261 -0
- package/test/patch/patch.test.ts +348 -0
- package/test/permission/arity.test.ts +33 -0
- package/test/permission/next.test.ts +652 -0
- package/test/permission-task.test.ts +319 -0
- package/test/plugin/codex.test.ts +123 -0
- package/test/preload.ts +65 -0
- package/test/project/project.test.ts +120 -0
- package/test/provider/amazon-bedrock.test.ts +268 -0
- package/test/provider/gitlab-duo.test.ts +286 -0
- package/test/provider/provider.test.ts +2149 -0
- package/test/provider/transform.test.ts +1566 -0
- package/test/question/question.test.ts +300 -0
- package/test/server/session-list.test.ts +39 -0
- package/test/server/session-select.test.ts +78 -0
- package/test/session/compaction.test.ts +293 -0
- package/test/session/llm.test.ts +90 -0
- package/test/session/message-v2.test.ts +662 -0
- package/test/session/retry.test.ts +131 -0
- package/test/session/revert-compact.test.ts +285 -0
- package/test/session/session.test.ts +71 -0
- package/test/skill/skill.test.ts +185 -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 +320 -0
- package/test/tool/external-directory.test.ts +126 -0
- package/test/tool/fixtures/large-image.png +0 -0
- package/test/tool/fixtures/models-api.json +33453 -0
- package/test/tool/grep.test.ts +109 -0
- package/test/tool/patch.test.ts +261 -0
- package/test/tool/read.test.ts +303 -0
- package/test/tool/registry.test.ts +76 -0
- package/test/tool/truncation.test.ts +159 -0
- package/test/util/filesystem.test.ts +39 -0
- package/test/util/format.test.ts +59 -0
- package/test/util/iife.test.ts +36 -0
- package/test/util/lazy.test.ts +50 -0
- package/test/util/lock.test.ts +72 -0
- package/test/util/timeout.test.ts +21 -0
- package/test/util/wildcard.test.ts +75 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { test, expect } from "bun:test"
|
|
2
|
+
import { Question } from "../../src/question"
|
|
3
|
+
import { Instance } from "../../src/project/instance"
|
|
4
|
+
import { tmpdir } from "../fixture/fixture"
|
|
5
|
+
|
|
6
|
+
test("ask - returns pending promise", async () => {
|
|
7
|
+
await using tmp = await tmpdir({ git: true })
|
|
8
|
+
await Instance.provide({
|
|
9
|
+
directory: tmp.path,
|
|
10
|
+
fn: async () => {
|
|
11
|
+
const promise = Question.ask({
|
|
12
|
+
sessionID: "ses_test",
|
|
13
|
+
questions: [
|
|
14
|
+
{
|
|
15
|
+
question: "What would you like to do?",
|
|
16
|
+
header: "Action",
|
|
17
|
+
options: [
|
|
18
|
+
{ label: "Option 1", description: "First option" },
|
|
19
|
+
{ label: "Option 2", description: "Second option" },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
})
|
|
24
|
+
expect(promise).toBeInstanceOf(Promise)
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("ask - adds to pending list", async () => {
|
|
30
|
+
await using tmp = await tmpdir({ git: true })
|
|
31
|
+
await Instance.provide({
|
|
32
|
+
directory: tmp.path,
|
|
33
|
+
fn: async () => {
|
|
34
|
+
const questions = [
|
|
35
|
+
{
|
|
36
|
+
question: "What would you like to do?",
|
|
37
|
+
header: "Action",
|
|
38
|
+
options: [
|
|
39
|
+
{ label: "Option 1", description: "First option" },
|
|
40
|
+
{ label: "Option 2", description: "Second option" },
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
Question.ask({
|
|
46
|
+
sessionID: "ses_test",
|
|
47
|
+
questions,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const pending = await Question.list()
|
|
51
|
+
expect(pending.length).toBe(1)
|
|
52
|
+
expect(pending[0].questions).toEqual(questions)
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// reply tests
|
|
58
|
+
|
|
59
|
+
test("reply - resolves the pending ask with answers", async () => {
|
|
60
|
+
await using tmp = await tmpdir({ git: true })
|
|
61
|
+
await Instance.provide({
|
|
62
|
+
directory: tmp.path,
|
|
63
|
+
fn: async () => {
|
|
64
|
+
const questions = [
|
|
65
|
+
{
|
|
66
|
+
question: "What would you like to do?",
|
|
67
|
+
header: "Action",
|
|
68
|
+
options: [
|
|
69
|
+
{ label: "Option 1", description: "First option" },
|
|
70
|
+
{ label: "Option 2", description: "Second option" },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
const askPromise = Question.ask({
|
|
76
|
+
sessionID: "ses_test",
|
|
77
|
+
questions,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const pending = await Question.list()
|
|
81
|
+
const requestID = pending[0].id
|
|
82
|
+
|
|
83
|
+
await Question.reply({
|
|
84
|
+
requestID,
|
|
85
|
+
answers: [["Option 1"]],
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const answers = await askPromise
|
|
89
|
+
expect(answers).toEqual([["Option 1"]])
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test("reply - removes from pending list", async () => {
|
|
95
|
+
await using tmp = await tmpdir({ git: true })
|
|
96
|
+
await Instance.provide({
|
|
97
|
+
directory: tmp.path,
|
|
98
|
+
fn: async () => {
|
|
99
|
+
Question.ask({
|
|
100
|
+
sessionID: "ses_test",
|
|
101
|
+
questions: [
|
|
102
|
+
{
|
|
103
|
+
question: "What would you like to do?",
|
|
104
|
+
header: "Action",
|
|
105
|
+
options: [
|
|
106
|
+
{ label: "Option 1", description: "First option" },
|
|
107
|
+
{ label: "Option 2", description: "Second option" },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const pending = await Question.list()
|
|
114
|
+
expect(pending.length).toBe(1)
|
|
115
|
+
|
|
116
|
+
await Question.reply({
|
|
117
|
+
requestID: pending[0].id,
|
|
118
|
+
answers: [["Option 1"]],
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const pendingAfter = await Question.list()
|
|
122
|
+
expect(pendingAfter.length).toBe(0)
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test("reply - does nothing for unknown requestID", async () => {
|
|
128
|
+
await using tmp = await tmpdir({ git: true })
|
|
129
|
+
await Instance.provide({
|
|
130
|
+
directory: tmp.path,
|
|
131
|
+
fn: async () => {
|
|
132
|
+
await Question.reply({
|
|
133
|
+
requestID: "que_unknown",
|
|
134
|
+
answers: [["Option 1"]],
|
|
135
|
+
})
|
|
136
|
+
// Should not throw
|
|
137
|
+
},
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// reject tests
|
|
142
|
+
|
|
143
|
+
test("reject - throws RejectedError", async () => {
|
|
144
|
+
await using tmp = await tmpdir({ git: true })
|
|
145
|
+
await Instance.provide({
|
|
146
|
+
directory: tmp.path,
|
|
147
|
+
fn: async () => {
|
|
148
|
+
const askPromise = Question.ask({
|
|
149
|
+
sessionID: "ses_test",
|
|
150
|
+
questions: [
|
|
151
|
+
{
|
|
152
|
+
question: "What would you like to do?",
|
|
153
|
+
header: "Action",
|
|
154
|
+
options: [
|
|
155
|
+
{ label: "Option 1", description: "First option" },
|
|
156
|
+
{ label: "Option 2", description: "Second option" },
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
const pending = await Question.list()
|
|
163
|
+
await Question.reject(pending[0].id)
|
|
164
|
+
|
|
165
|
+
await expect(askPromise).rejects.toBeInstanceOf(Question.RejectedError)
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test("reject - removes from pending list", async () => {
|
|
171
|
+
await using tmp = await tmpdir({ git: true })
|
|
172
|
+
await Instance.provide({
|
|
173
|
+
directory: tmp.path,
|
|
174
|
+
fn: async () => {
|
|
175
|
+
const askPromise = Question.ask({
|
|
176
|
+
sessionID: "ses_test",
|
|
177
|
+
questions: [
|
|
178
|
+
{
|
|
179
|
+
question: "What would you like to do?",
|
|
180
|
+
header: "Action",
|
|
181
|
+
options: [
|
|
182
|
+
{ label: "Option 1", description: "First option" },
|
|
183
|
+
{ label: "Option 2", description: "Second option" },
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
const pending = await Question.list()
|
|
190
|
+
expect(pending.length).toBe(1)
|
|
191
|
+
|
|
192
|
+
await Question.reject(pending[0].id)
|
|
193
|
+
askPromise.catch(() => {}) // Ignore rejection
|
|
194
|
+
|
|
195
|
+
const pendingAfter = await Question.list()
|
|
196
|
+
expect(pendingAfter.length).toBe(0)
|
|
197
|
+
},
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
test("reject - does nothing for unknown requestID", async () => {
|
|
202
|
+
await using tmp = await tmpdir({ git: true })
|
|
203
|
+
await Instance.provide({
|
|
204
|
+
directory: tmp.path,
|
|
205
|
+
fn: async () => {
|
|
206
|
+
await Question.reject("que_unknown")
|
|
207
|
+
// Should not throw
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// multiple questions tests
|
|
213
|
+
|
|
214
|
+
test("ask - handles multiple questions", async () => {
|
|
215
|
+
await using tmp = await tmpdir({ git: true })
|
|
216
|
+
await Instance.provide({
|
|
217
|
+
directory: tmp.path,
|
|
218
|
+
fn: async () => {
|
|
219
|
+
const questions = [
|
|
220
|
+
{
|
|
221
|
+
question: "What would you like to do?",
|
|
222
|
+
header: "Action",
|
|
223
|
+
options: [
|
|
224
|
+
{ label: "Build", description: "Build the project" },
|
|
225
|
+
{ label: "Test", description: "Run tests" },
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
question: "Which environment?",
|
|
230
|
+
header: "Env",
|
|
231
|
+
options: [
|
|
232
|
+
{ label: "Dev", description: "Development" },
|
|
233
|
+
{ label: "Prod", description: "Production" },
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
const askPromise = Question.ask({
|
|
239
|
+
sessionID: "ses_test",
|
|
240
|
+
questions,
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
const pending = await Question.list()
|
|
244
|
+
|
|
245
|
+
await Question.reply({
|
|
246
|
+
requestID: pending[0].id,
|
|
247
|
+
answers: [["Build"], ["Dev"]],
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
const answers = await askPromise
|
|
251
|
+
expect(answers).toEqual([["Build"], ["Dev"]])
|
|
252
|
+
},
|
|
253
|
+
})
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
// list tests
|
|
257
|
+
|
|
258
|
+
test("list - returns all pending requests", async () => {
|
|
259
|
+
await using tmp = await tmpdir({ git: true })
|
|
260
|
+
await Instance.provide({
|
|
261
|
+
directory: tmp.path,
|
|
262
|
+
fn: async () => {
|
|
263
|
+
Question.ask({
|
|
264
|
+
sessionID: "ses_test1",
|
|
265
|
+
questions: [
|
|
266
|
+
{
|
|
267
|
+
question: "Question 1?",
|
|
268
|
+
header: "Q1",
|
|
269
|
+
options: [{ label: "A", description: "A" }],
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
Question.ask({
|
|
275
|
+
sessionID: "ses_test2",
|
|
276
|
+
questions: [
|
|
277
|
+
{
|
|
278
|
+
question: "Question 2?",
|
|
279
|
+
header: "Q2",
|
|
280
|
+
options: [{ label: "B", description: "B" }],
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
const pending = await Question.list()
|
|
286
|
+
expect(pending.length).toBe(2)
|
|
287
|
+
},
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
test("list - returns empty when no pending", async () => {
|
|
292
|
+
await using tmp = await tmpdir({ git: true })
|
|
293
|
+
await Instance.provide({
|
|
294
|
+
directory: tmp.path,
|
|
295
|
+
fn: async () => {
|
|
296
|
+
const pending = await Question.list()
|
|
297
|
+
expect(pending.length).toBe(0)
|
|
298
|
+
},
|
|
299
|
+
})
|
|
300
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import { Instance } from "../../src/project/instance"
|
|
4
|
+
import { Server } from "../../src/server/server"
|
|
5
|
+
import { Session } from "../../src/session"
|
|
6
|
+
import { Log } from "../../src/util/log"
|
|
7
|
+
|
|
8
|
+
const projectRoot = path.join(__dirname, "../..")
|
|
9
|
+
Log.init({ print: false })
|
|
10
|
+
|
|
11
|
+
describe("session.list", () => {
|
|
12
|
+
test("filters by directory", async () => {
|
|
13
|
+
await Instance.provide({
|
|
14
|
+
directory: projectRoot,
|
|
15
|
+
fn: async () => {
|
|
16
|
+
const app = Server.App()
|
|
17
|
+
|
|
18
|
+
const first = await Session.create({})
|
|
19
|
+
|
|
20
|
+
const otherDir = path.join(projectRoot, "..", "__session_list_other")
|
|
21
|
+
const second = await Instance.provide({
|
|
22
|
+
directory: otherDir,
|
|
23
|
+
fn: async () => Session.create({}),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const response = await app.request(`/session?directory=${encodeURIComponent(projectRoot)}`)
|
|
27
|
+
expect(response.status).toBe(200)
|
|
28
|
+
|
|
29
|
+
const body = (await response.json()) as unknown[]
|
|
30
|
+
const ids = body
|
|
31
|
+
.map((s) => (typeof s === "object" && s && "id" in s ? (s as { id: string }).id : undefined))
|
|
32
|
+
.filter((x): x is string => typeof x === "string")
|
|
33
|
+
|
|
34
|
+
expect(ids).toContain(first.id)
|
|
35
|
+
expect(ids).not.toContain(second.id)
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
})
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import { Session } from "../../src/session"
|
|
4
|
+
import { Log } from "../../src/util/log"
|
|
5
|
+
import { Instance } from "../../src/project/instance"
|
|
6
|
+
import { Server } from "../../src/server/server"
|
|
7
|
+
|
|
8
|
+
const projectRoot = path.join(__dirname, "../..")
|
|
9
|
+
Log.init({ print: false })
|
|
10
|
+
|
|
11
|
+
describe("tui.selectSession endpoint", () => {
|
|
12
|
+
test("should return 200 when called with valid session", async () => {
|
|
13
|
+
await Instance.provide({
|
|
14
|
+
directory: projectRoot,
|
|
15
|
+
fn: async () => {
|
|
16
|
+
// #given
|
|
17
|
+
const session = await Session.create({})
|
|
18
|
+
|
|
19
|
+
// #when
|
|
20
|
+
const app = Server.App()
|
|
21
|
+
const response = await app.request("/tui/select-session", {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: { "Content-Type": "application/json" },
|
|
24
|
+
body: JSON.stringify({ sessionID: session.id }),
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// #then
|
|
28
|
+
expect(response.status).toBe(200)
|
|
29
|
+
const body = await response.json()
|
|
30
|
+
expect(body).toBe(true)
|
|
31
|
+
|
|
32
|
+
await Session.remove(session.id)
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test("should return 404 when session does not exist", async () => {
|
|
38
|
+
await Instance.provide({
|
|
39
|
+
directory: projectRoot,
|
|
40
|
+
fn: async () => {
|
|
41
|
+
// #given
|
|
42
|
+
const nonExistentSessionID = "ses_nonexistent123"
|
|
43
|
+
|
|
44
|
+
// #when
|
|
45
|
+
const app = Server.App()
|
|
46
|
+
const response = await app.request("/tui/select-session", {
|
|
47
|
+
method: "POST",
|
|
48
|
+
headers: { "Content-Type": "application/json" },
|
|
49
|
+
body: JSON.stringify({ sessionID: nonExistentSessionID }),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// #then
|
|
53
|
+
expect(response.status).toBe(404)
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("should return 400 when session ID format is invalid", async () => {
|
|
59
|
+
await Instance.provide({
|
|
60
|
+
directory: projectRoot,
|
|
61
|
+
fn: async () => {
|
|
62
|
+
// #given
|
|
63
|
+
const invalidSessionID = "invalid_session_id"
|
|
64
|
+
|
|
65
|
+
// #when
|
|
66
|
+
const app = Server.App()
|
|
67
|
+
const response = await app.request("/tui/select-session", {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: { "Content-Type": "application/json" },
|
|
70
|
+
body: JSON.stringify({ sessionID: invalidSessionID }),
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// #then
|
|
74
|
+
expect(response.status).toBe(400)
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
})
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import { SessionCompaction } from "../../src/session/compaction"
|
|
4
|
+
import { Token } from "../../src/util/token"
|
|
5
|
+
import { Instance } from "../../src/project/instance"
|
|
6
|
+
import { Log } from "../../src/util/log"
|
|
7
|
+
import { tmpdir } from "../fixture/fixture"
|
|
8
|
+
import { Session } from "../../src/session"
|
|
9
|
+
import type { Provider } from "../../src/provider/provider"
|
|
10
|
+
|
|
11
|
+
Log.init({ print: false })
|
|
12
|
+
|
|
13
|
+
function createModel(opts: {
|
|
14
|
+
context: number
|
|
15
|
+
output: number
|
|
16
|
+
input?: number
|
|
17
|
+
cost?: Provider.Model["cost"]
|
|
18
|
+
}): Provider.Model {
|
|
19
|
+
return {
|
|
20
|
+
id: "test-model",
|
|
21
|
+
providerID: "test",
|
|
22
|
+
name: "Test",
|
|
23
|
+
limit: {
|
|
24
|
+
context: opts.context,
|
|
25
|
+
input: opts.input,
|
|
26
|
+
output: opts.output,
|
|
27
|
+
},
|
|
28
|
+
cost: opts.cost ?? { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
|
29
|
+
capabilities: {
|
|
30
|
+
toolcall: true,
|
|
31
|
+
attachment: false,
|
|
32
|
+
reasoning: false,
|
|
33
|
+
temperature: true,
|
|
34
|
+
input: { text: true, image: false, audio: false, video: false },
|
|
35
|
+
output: { text: true, image: false, audio: false, video: false },
|
|
36
|
+
},
|
|
37
|
+
api: { npm: "@ai-sdk/anthropic" },
|
|
38
|
+
options: {},
|
|
39
|
+
} as Provider.Model
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("session.compaction.isOverflow", () => {
|
|
43
|
+
test("returns true when token count exceeds usable context", async () => {
|
|
44
|
+
await using tmp = await tmpdir()
|
|
45
|
+
await Instance.provide({
|
|
46
|
+
directory: tmp.path,
|
|
47
|
+
fn: async () => {
|
|
48
|
+
const model = createModel({ context: 100_000, output: 32_000 })
|
|
49
|
+
const tokens = { input: 75_000, output: 5_000, reasoning: 0, cache: { read: 0, write: 0 } }
|
|
50
|
+
expect(await SessionCompaction.isOverflow({ tokens, model })).toBe(true)
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test("returns false when token count within usable context", async () => {
|
|
56
|
+
await using tmp = await tmpdir()
|
|
57
|
+
await Instance.provide({
|
|
58
|
+
directory: tmp.path,
|
|
59
|
+
fn: async () => {
|
|
60
|
+
const model = createModel({ context: 200_000, output: 32_000 })
|
|
61
|
+
const tokens = { input: 100_000, output: 10_000, reasoning: 0, cache: { read: 0, write: 0 } }
|
|
62
|
+
expect(await SessionCompaction.isOverflow({ tokens, model })).toBe(false)
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test("includes cache.read in token count", async () => {
|
|
68
|
+
await using tmp = await tmpdir()
|
|
69
|
+
await Instance.provide({
|
|
70
|
+
directory: tmp.path,
|
|
71
|
+
fn: async () => {
|
|
72
|
+
const model = createModel({ context: 100_000, output: 32_000 })
|
|
73
|
+
const tokens = { input: 50_000, output: 10_000, reasoning: 0, cache: { read: 10_000, write: 0 } }
|
|
74
|
+
expect(await SessionCompaction.isOverflow({ tokens, model })).toBe(true)
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test("respects input limit for input caps", async () => {
|
|
80
|
+
await using tmp = await tmpdir()
|
|
81
|
+
await Instance.provide({
|
|
82
|
+
directory: tmp.path,
|
|
83
|
+
fn: async () => {
|
|
84
|
+
const model = createModel({ context: 400_000, input: 272_000, output: 128_000 })
|
|
85
|
+
const tokens = { input: 271_000, output: 1_000, reasoning: 0, cache: { read: 2_000, write: 0 } }
|
|
86
|
+
expect(await SessionCompaction.isOverflow({ tokens, model })).toBe(true)
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test("returns false when input/output are within input caps", async () => {
|
|
92
|
+
await using tmp = await tmpdir()
|
|
93
|
+
await Instance.provide({
|
|
94
|
+
directory: tmp.path,
|
|
95
|
+
fn: async () => {
|
|
96
|
+
const model = createModel({ context: 400_000, input: 272_000, output: 128_000 })
|
|
97
|
+
const tokens = { input: 200_000, output: 20_000, reasoning: 0, cache: { read: 10_000, write: 0 } }
|
|
98
|
+
expect(await SessionCompaction.isOverflow({ tokens, model })).toBe(false)
|
|
99
|
+
},
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test("returns false when output within limit with input caps", async () => {
|
|
104
|
+
await using tmp = await tmpdir()
|
|
105
|
+
await Instance.provide({
|
|
106
|
+
directory: tmp.path,
|
|
107
|
+
fn: async () => {
|
|
108
|
+
const model = createModel({ context: 200_000, input: 120_000, output: 10_000 })
|
|
109
|
+
const tokens = { input: 50_000, output: 9_999, reasoning: 0, cache: { read: 0, write: 0 } }
|
|
110
|
+
expect(await SessionCompaction.isOverflow({ tokens, model })).toBe(false)
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test("returns false when model context limit is 0", async () => {
|
|
116
|
+
await using tmp = await tmpdir()
|
|
117
|
+
await Instance.provide({
|
|
118
|
+
directory: tmp.path,
|
|
119
|
+
fn: async () => {
|
|
120
|
+
const model = createModel({ context: 0, output: 32_000 })
|
|
121
|
+
const tokens = { input: 100_000, output: 10_000, reasoning: 0, cache: { read: 0, write: 0 } }
|
|
122
|
+
expect(await SessionCompaction.isOverflow({ tokens, model })).toBe(false)
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test("returns false when compaction.auto is disabled", async () => {
|
|
128
|
+
await using tmp = await tmpdir({
|
|
129
|
+
init: async (dir) => {
|
|
130
|
+
await Bun.write(
|
|
131
|
+
path.join(dir, "opencode.json"),
|
|
132
|
+
JSON.stringify({
|
|
133
|
+
compaction: { auto: false },
|
|
134
|
+
}),
|
|
135
|
+
)
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
await Instance.provide({
|
|
139
|
+
directory: tmp.path,
|
|
140
|
+
fn: async () => {
|
|
141
|
+
const model = createModel({ context: 100_000, output: 32_000 })
|
|
142
|
+
const tokens = { input: 75_000, output: 5_000, reasoning: 0, cache: { read: 0, write: 0 } }
|
|
143
|
+
expect(await SessionCompaction.isOverflow({ tokens, model })).toBe(false)
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
describe("util.token.estimate", () => {
|
|
150
|
+
test("estimates tokens from text (4 chars per token)", () => {
|
|
151
|
+
const text = "x".repeat(4000)
|
|
152
|
+
expect(Token.estimate(text)).toBe(1000)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test("estimates tokens from larger text", () => {
|
|
156
|
+
const text = "y".repeat(20_000)
|
|
157
|
+
expect(Token.estimate(text)).toBe(5000)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test("returns 0 for empty string", () => {
|
|
161
|
+
expect(Token.estimate("")).toBe(0)
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
describe("session.getUsage", () => {
|
|
166
|
+
test("normalizes standard usage to token format", () => {
|
|
167
|
+
const model = createModel({ context: 100_000, output: 32_000 })
|
|
168
|
+
const result = Session.getUsage({
|
|
169
|
+
model,
|
|
170
|
+
usage: {
|
|
171
|
+
inputTokens: 1000,
|
|
172
|
+
outputTokens: 500,
|
|
173
|
+
totalTokens: 1500,
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
expect(result.tokens.input).toBe(1000)
|
|
178
|
+
expect(result.tokens.output).toBe(500)
|
|
179
|
+
expect(result.tokens.reasoning).toBe(0)
|
|
180
|
+
expect(result.tokens.cache.read).toBe(0)
|
|
181
|
+
expect(result.tokens.cache.write).toBe(0)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test("extracts cached tokens to cache.read", () => {
|
|
185
|
+
const model = createModel({ context: 100_000, output: 32_000 })
|
|
186
|
+
const result = Session.getUsage({
|
|
187
|
+
model,
|
|
188
|
+
usage: {
|
|
189
|
+
inputTokens: 1000,
|
|
190
|
+
outputTokens: 500,
|
|
191
|
+
totalTokens: 1500,
|
|
192
|
+
cachedInputTokens: 200,
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
expect(result.tokens.input).toBe(800)
|
|
197
|
+
expect(result.tokens.cache.read).toBe(200)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test("handles anthropic cache write metadata", () => {
|
|
201
|
+
const model = createModel({ context: 100_000, output: 32_000 })
|
|
202
|
+
const result = Session.getUsage({
|
|
203
|
+
model,
|
|
204
|
+
usage: {
|
|
205
|
+
inputTokens: 1000,
|
|
206
|
+
outputTokens: 500,
|
|
207
|
+
totalTokens: 1500,
|
|
208
|
+
},
|
|
209
|
+
metadata: {
|
|
210
|
+
anthropic: {
|
|
211
|
+
cacheCreationInputTokens: 300,
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
expect(result.tokens.cache.write).toBe(300)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test("does not subtract cached tokens for anthropic provider", () => {
|
|
220
|
+
const model = createModel({ context: 100_000, output: 32_000 })
|
|
221
|
+
const result = Session.getUsage({
|
|
222
|
+
model,
|
|
223
|
+
usage: {
|
|
224
|
+
inputTokens: 1000,
|
|
225
|
+
outputTokens: 500,
|
|
226
|
+
totalTokens: 1500,
|
|
227
|
+
cachedInputTokens: 200,
|
|
228
|
+
},
|
|
229
|
+
metadata: {
|
|
230
|
+
anthropic: {},
|
|
231
|
+
},
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
expect(result.tokens.input).toBe(1000)
|
|
235
|
+
expect(result.tokens.cache.read).toBe(200)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test("handles reasoning tokens", () => {
|
|
239
|
+
const model = createModel({ context: 100_000, output: 32_000 })
|
|
240
|
+
const result = Session.getUsage({
|
|
241
|
+
model,
|
|
242
|
+
usage: {
|
|
243
|
+
inputTokens: 1000,
|
|
244
|
+
outputTokens: 500,
|
|
245
|
+
totalTokens: 1500,
|
|
246
|
+
reasoningTokens: 100,
|
|
247
|
+
},
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
expect(result.tokens.reasoning).toBe(100)
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
test("handles undefined optional values gracefully", () => {
|
|
254
|
+
const model = createModel({ context: 100_000, output: 32_000 })
|
|
255
|
+
const result = Session.getUsage({
|
|
256
|
+
model,
|
|
257
|
+
usage: {
|
|
258
|
+
inputTokens: 0,
|
|
259
|
+
outputTokens: 0,
|
|
260
|
+
totalTokens: 0,
|
|
261
|
+
},
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
expect(result.tokens.input).toBe(0)
|
|
265
|
+
expect(result.tokens.output).toBe(0)
|
|
266
|
+
expect(result.tokens.reasoning).toBe(0)
|
|
267
|
+
expect(result.tokens.cache.read).toBe(0)
|
|
268
|
+
expect(result.tokens.cache.write).toBe(0)
|
|
269
|
+
expect(Number.isNaN(result.cost)).toBe(false)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
test("calculates cost correctly", () => {
|
|
273
|
+
const model = createModel({
|
|
274
|
+
context: 100_000,
|
|
275
|
+
output: 32_000,
|
|
276
|
+
cost: {
|
|
277
|
+
input: 3,
|
|
278
|
+
output: 15,
|
|
279
|
+
cache: { read: 0.3, write: 3.75 },
|
|
280
|
+
},
|
|
281
|
+
})
|
|
282
|
+
const result = Session.getUsage({
|
|
283
|
+
model,
|
|
284
|
+
usage: {
|
|
285
|
+
inputTokens: 1_000_000,
|
|
286
|
+
outputTokens: 100_000,
|
|
287
|
+
totalTokens: 1_100_000,
|
|
288
|
+
},
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
expect(result.cost).toBe(3 + 1.5)
|
|
292
|
+
})
|
|
293
|
+
})
|