opencode-qoder-bridge 0.1.8 → 0.1.10

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +70 -1
  2. package/README.md +112 -45
  3. package/bin/statusline.mjs +30 -6
  4. package/bin/usage.mjs +6 -5
  5. package/dist/async-utils.d.ts +8 -0
  6. package/dist/async-utils.d.ts.map +1 -0
  7. package/dist/async-utils.js +27 -0
  8. package/dist/async-utils.js.map +1 -0
  9. package/dist/auth.d.ts +2 -1
  10. package/dist/auth.d.ts.map +1 -1
  11. package/dist/auth.js +26 -7
  12. package/dist/auth.js.map +1 -1
  13. package/dist/cost.d.ts +2 -1
  14. package/dist/cost.d.ts.map +1 -1
  15. package/dist/cost.js +163 -63
  16. package/dist/cost.js.map +1 -1
  17. package/dist/errors.d.ts.map +1 -1
  18. package/dist/errors.js +5 -2
  19. package/dist/errors.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +187 -41
  23. package/dist/index.js.map +1 -1
  24. package/dist/language-model.d.ts +25 -0
  25. package/dist/language-model.d.ts.map +1 -1
  26. package/dist/language-model.js +938 -183
  27. package/dist/language-model.js.map +1 -1
  28. package/dist/logger.d.ts +1 -0
  29. package/dist/logger.d.ts.map +1 -1
  30. package/dist/logger.js +35 -5
  31. package/dist/logger.js.map +1 -1
  32. package/dist/mcp-bridge.d.ts.map +1 -1
  33. package/dist/mcp-bridge.js +71 -7
  34. package/dist/mcp-bridge.js.map +1 -1
  35. package/dist/models.d.ts +25 -6
  36. package/dist/models.d.ts.map +1 -1
  37. package/dist/models.js +444 -85
  38. package/dist/models.js.map +1 -1
  39. package/dist/prompt-builder.d.ts.map +1 -1
  40. package/dist/prompt-builder.js +241 -43
  41. package/dist/prompt-builder.js.map +1 -1
  42. package/dist/sdk-auth.d.ts +3 -3
  43. package/dist/sdk-auth.d.ts.map +1 -1
  44. package/dist/sdk-auth.js +13 -9
  45. package/dist/sdk-auth.js.map +1 -1
  46. package/dist/sdk-session.d.ts.map +1 -1
  47. package/dist/sdk-session.js +26 -2
  48. package/dist/sdk-session.js.map +1 -1
  49. package/dist/session-store.d.ts +14 -3
  50. package/dist/session-store.d.ts.map +1 -1
  51. package/dist/session-store.js +451 -46
  52. package/dist/session-store.js.map +1 -1
  53. package/dist/state-dir.d.ts.map +1 -1
  54. package/dist/state-dir.js +2 -1
  55. package/dist/state-dir.js.map +1 -1
  56. package/dist/tool-normalizer.d.ts.map +1 -1
  57. package/dist/tool-normalizer.js +11 -2
  58. package/dist/tool-normalizer.js.map +1 -1
  59. package/dist/tui-register.d.ts.map +1 -1
  60. package/dist/tui-register.js +88 -37
  61. package/dist/tui-register.js.map +1 -1
  62. package/dist/tui.d.ts.map +1 -1
  63. package/dist/tui.js +42 -17
  64. package/dist/tui.js.map +1 -1
  65. package/dist/types.d.ts +16 -4
  66. package/dist/types.d.ts.map +1 -1
  67. package/dist/usage.d.ts.map +1 -1
  68. package/dist/usage.js +54 -29
  69. package/dist/usage.js.map +1 -1
  70. package/package.json +12 -4
package/CHANGELOG.md CHANGED
@@ -4,6 +4,71 @@ All notable changes to this project are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and releases use
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.1.10] - 2026-08-30
8
+
9
+ ### Changed
10
+
11
+ - Model discovery now completes a bounded live Qoder catalog lookup during
12
+ plugin startup, so newly available account models are registered
13
+ automatically without a manual model list or cache warm-up restart.
14
+ - Discovery uses the SDK's bundled Worker runtime first, falls back to an
15
+ installed CLI automatically when needed, scopes caches by deployment and
16
+ credential context, and ignores empty or late responses that could otherwise
17
+ replace a known-good catalog.
18
+
19
+ ## [0.1.9] - 2026-08-30
20
+
21
+ ### Added
22
+
23
+ - Updated `@qoder-ai/qoder-agent-sdk` to `1.0.30` (paired with `qodercli` 1.1.35).
24
+ - **Plan Mode Integration**: Added `planMode?: boolean` provider option to run in planning mode independently from tool permissions. Registered new `/qoder_plan_mode` tool.
25
+ - **Outbound Proxy Routing**: Added `proxy?: string` provider option for direct qodercli HTTP/HTTPS/SOCKS5 proxy routing without mutating environment variables, with automatic fallback to `HTTPS_PROXY` / `HTTP_PROXY`.
26
+ - **Skill Evolution**: Added `evolution?: EvolutionOptions` provider option to enable autonomous turn-completion skill analysis and recommendations.
27
+ - **Dynamic Live Model Catalog Updates**: Ingests real-time `available_models_update` stream events to synchronize model availability and persistent cache without restarting OpenCode.
28
+ - **Artifacts Tracking**: Ingests `artifacts_update` events during chat turns and surfaces artifact change metadata in `providerMetadata.qoder.artifacts`.
29
+ - **Sessions Tool**: Added `qoder_sessions` tool using SDK's `listSessions()` to inspect recent sessions, titles, and git branches.
30
+
31
+ ### Fixed
32
+
33
+ - **Runtime Package Boundary**: Moved the runtime-used `@opencode-ai/plugin`
34
+ helper into production dependencies and verified root/provider imports from a
35
+ dev-omitted packed install.
36
+ - **Workspace-Aware Session Safety**: Scoped persisted session mappings by
37
+ workspace, serialized same-key turns, delayed mapping writes until a
38
+ successful turn, and added bounded cross-process state locking.
39
+ - **Catalog, Usage, and Stream Hardening**: Made live model snapshots
40
+ authoritative, bounded discovery/usage requests, closed abort and duplicate
41
+ event races, sanitized malformed SDK usage/metadata, and surfaced typed
42
+ authentication/result failures.
43
+ - **Persistent State and Input Bounds**: Hardened atomic state/config writes,
44
+ model/session cache validation, prompt/image limits, MCP URL/command input,
45
+ and finite cost/statusline accounting.
46
+ - **Unknown Model Transparency**: Unknown requested model IDs are now forwarded
47
+ unchanged to Qoder while the default catalog entry is used only for prompt
48
+ sizing metadata; the bridge no longer silently selects `auto`.
49
+ - **Ephemeral Session Privacy**: Explicitly disables SDK transcript persistence
50
+ for non-persistent turns and auxiliary usage/model discovery queries.
51
+ - **Stream and Boundary Hardening**: Added bounded turn timeouts, abort-aware
52
+ generation failures, bounded query cleanup, malformed-stream rejection,
53
+ replay UUID guards, tool ownership deny rules, diagnostic redaction, and
54
+ stricter MCP URL/command validation.
55
+ - **Release Artifact Safety**: Added a `prepack` rebuild hook, executable usage
56
+ CLI permissions, and a public `./errors` export for the typed error API.
57
+ - **Session Store Concurrency Race Condition**: Added an async mutex queue (`withLock`) to serialize concurrent `ensureQoderSession` and `deleteQoderSession` operations, preventing dropped session records under parallel turns.
58
+ - **Tool Continuation History Trimming**: Fixed a critical bug in `trimToBudget` where the active user prompt was dropped during continuation turns with large assistant/tool outputs, preventing prompts from falling back to `"Hello"`.
59
+ - **Data URL Parsing Hardening**: Added bounded-length and round-trip base64 validation with early rejection of oversized image attachments, preventing avoidable CPU and memory pressure from malformed multi-megabyte inputs.
60
+ - **Stream Controller Abort Safety & Listener Cleanup**: Added `safeEnqueue` and `safeClose` to prevent uncaught `Invalid state: Controller is already closed` exceptions when streams are aborted or cancelled, and cleaned up `abortSignal` event listeners in `language-model.ts` and `sdk-session.ts` to prevent memory leaks on shared signals.
61
+ - **`doGenerate` Provider Metadata**: Fixed `doGenerate` discarding `providerMetadata` (`totalCostUSD`, `contextUsageRatio`, `planMode`, `artifacts`), ensuring parity with `doStream`.
62
+ - **Coalesced Model Cache Writes**: Implemented a coalesced cache write queue in `models.ts` to prevent simultaneous file write contention and eliminate orphaned `.tmp` files under rapid streaming catalog updates.
63
+ - **Atomic File Cleanup & All-Session Reset**: Added `try...finally` temporary file unlinking in `session-store.ts` and `models.ts` to guarantee zero dangling `.tmp` files, and exported `clearAllSessions()` to support full session resets.
64
+ - **Tool Schema & Error Boundaries**: Upgraded plugin tools with `tool()` and `tool.schema` from `@opencode-ai/plugin`, added `key` (and `"all"`) to `qoder_session_reset`, added `dir` and `limit` filtering to `qoder_sessions`, and wrapped tool executions in error boundaries.
65
+ - **Tool Normalization & Path Aliases**: Added `execute_command`, `executecommand`, `run_command`, and `runcommand` mapping to `bash`, added `cmd` alias for command, and added `path` alias for `filePath` across read, write, edit, delete, view, and apply_diff.
66
+ - **MCP Bridge Argument Flexibility**: Converted numeric and boolean arguments in `mcp-bridge` stdio configs without dropping the argument list.
67
+ - **Prompt Serialization Hardening**: Added non-image file attachment serialization in `prompt-builder` so attached text files are preserved, and safely guarded missing `toolCallId` / `toolName`.
68
+ - **TUI & CLI Hardening**: Guarded `latest.model?.providerID` in `tui.ts` against undefined model objects, guarded against non-finite costs (`NaN`) in sidebar and statusline, and trimmed PAT whitespace in `hasQoderPAT()`.
69
+ - **Stress Test Suite**: Added `test/stress.test.mjs` verifying concurrency, massive conversations (1,000 messages), stream aborts, and prototype pollution protection.
70
+ - **CI-Safe Live Stress Coverage**: Made the Qoder-backed stream-abort stress probe opt-in with `QODER_STRESS_E2E=1`, keeping the normal test suite credential-free while preserving live stress coverage when explicitly enabled.
71
+
7
72
  ## [0.1.8] - 2026-08-26
8
73
 
9
74
  ### Fixed
@@ -78,7 +143,9 @@ All notable changes to this project are documented here. The format follows
78
143
  session.
79
144
  - The `models.json` cache is written atomically with mode 0600 instead of a
80
145
  direct partial-write-prone write.
81
- - Unknown model IDs fall back to the default model with a debug log entry.
146
+ - Unknown model IDs previously fell back to the default model with a debug log
147
+ entry; current releases preserve the requested ID and let Qoder report model
148
+ availability explicitly.
82
149
 
83
150
  ## [0.1.3] - 2026-08-20
84
151
 
@@ -155,3 +222,5 @@ also verified with an in-process MCP transport.
155
222
  [0.1.4]: https://github.com/naoufalelbani/opencode-qoder-bridge/compare/v0.1.3...v0.1.4
156
223
  [0.1.5]: https://github.com/naoufalelbani/opencode-qoder-bridge/compare/v0.1.4...v0.1.5
157
224
  [0.1.8]: https://github.com/naoufalelbani/opencode-qoder-bridge/compare/v0.1.7...v0.1.8
225
+ [0.1.9]: https://github.com/naoufalelbani/opencode-qoder-bridge/compare/v0.1.8...v0.1.9
226
+ [0.1.10]: https://github.com/naoufalelbani/opencode-qoder-bridge/compare/v0.1.9...v0.1.10
package/README.md CHANGED
@@ -119,42 +119,19 @@ The ledger accumulates across sessions. Delete `~/.config/opencode-qoder-bridge/
119
119
 
120
120
  The bridge discovers the available catalog at startup through the SDK's
121
121
  `getAvailableModels()` API. Availability is account-, region-, rollout-, and
122
- SDK-version-dependent. A live query on **2026-08-20** returned:
123
-
124
- | Model ID | Name | Credit multiplier | Vision | Reasoning | Max input | Max output |
125
- |----------|------|-------------------|--------|-----------|-----------|------------|
126
-
127
- The following are known or historical model IDs. Some may be obsolete or
128
- unavailable for a particular account and are not an availability guarantee:
129
-
130
- | Model ID | Name | Credit multiplier | Vision | Reasoning | Max input | Max output |
131
- |----------|------|-------------------|--------|-----------|-----------|------------|
132
- | `auto` | Auto | 1.00x | ✓ | ✗ | 180K | 32K |
133
- | `ultimate` | Ultimate | 0.80x | ✓ | ✓ | 1M | 32K |
134
- | `performance` | Performance | 1.10x | ✓ | ✗ | 1M | 32K |
135
- | `efficient` | Efficient | 0.30x | ✓ | ✗ | 180K | 32K |
136
- | `lite` | Lite | 0.00x | ✗ | ✗ | 180K | 32K |
137
- | `cmodel` | Cantus | 1.60x | ✓ | ✓ | 180K | 32K |
138
- | `qmodel_preview` | Qwen3.8-Max-Preview | 0.01x promo | ✓ | ✓ | 180K | 32K |
139
- | `qmodel_38max` | Qwen3.8-Max | 0.25x | ✓ | ✓ | 180K | 32K |
140
- | `qmodel_latest` | Qwen3.7-Max | 0.10x promo | ✓ | ✗ | 1M | 32K |
141
- | `qmodel` | Qwen3.7-Plus | 0.04x promo | ✓ | ✗ | 1M | 32K |
142
- | `kmodel_latest` | Kimi-K3 | 0.80x | ✓ | ✗ | 180K | 32K |
143
- | `kmodel` | Kimi-K2.7-Code | 0.30x | ✓ | ✗ | 256K | 32K |
144
- | `gm51model` | GLM-5.2 | 0.50x promo | ✓ | ✓ | 1M | 32K |
145
- | `dmodel` | DeepSeek-V4-Pro | 0.50x | ✓ | ✓ | 1M | 32K |
146
- | `dfmodel` | DeepSeek-V4-Flash | 0.10x | ✓ | ✓ | 1M | 32K |
147
- | `mmodel` | MiniMax-M3 | 0.20x | ✓ | ✗ | 1M | 32K |
148
-
149
- This table is a snapshot, not a hard-coded allowlist. Qoder can vary model
150
- availability by account, plan, CLI version, or staged rollout. Promotional
151
- multipliers are time-dependent; the SDK's current `priceFactor` is
152
- authoritative. Restart OpenCode to refresh the bridge's in-process model cache.
122
+ SDK-version-dependent. A permanent allowlist is intentionally not embedded
123
+ here because the catalog is account- and scene-dependent.
124
+
125
+ There is intentionally no static model table here. Model availability is
126
+ account-, region-, plan-, scene-, SDK-version-, and rollout-dependent, and the
127
+ SDK's live catalog is authoritative for selectable IDs, capabilities, context
128
+ limits, and pricing.
153
129
 
154
130
  Run `opencode models qoder` to inspect the models currently registered with
155
131
  OpenCode. The `qoder_models` tool also exposes capabilities, context limits,
156
- and price multipliers to the agent. Model discovery is cached and refreshed in
157
- the background so network or authentication latency does not block startup.
132
+ and price multipliers to the agent. On each plugin startup, the bridge performs
133
+ bounded live discovery automatically; if Qoder is unavailable, it uses the
134
+ last catalog for the same credential/deployment context and the built-ins.
158
135
 
159
136
  ## Configuration
160
137
 
@@ -172,7 +149,13 @@ Bridge opencode MCP servers into the SDK by passing provider options:
172
149
  }
173
150
  ```
174
151
 
152
+ Flag names may be written with or without the leading `--`.
153
+
175
154
  `config.mcp` servers are bridged into the SDK's `mcpServers` automatically.
155
+ Chat turns have a 30-minute bridge timeout by default; set `options.timeoutMs`
156
+ to a positive value to use a shorter or longer bounded timeout (up to 24 hours).
157
+ Values in `options.env` override inherited process variables rather than
158
+ replacing the complete child environment.
176
159
 
177
160
  ### Persistent sessions and permissions
178
161
 
@@ -194,9 +177,15 @@ Session persistence is opt-in. Give a provider configuration a stable
194
177
 
195
178
  Mappings are stored in
196
179
  `~/.config/opencode-qoder-bridge/sessions.json` with restrictive file
197
- permissions. Use the `qoder_session_reset` tool to forget the mapping. A new
198
- session is created automatically if the mapping does not exist; existing
199
- sessions are resumed through the Qoder SDK.
180
+ permissions and are scoped to the configured working directory. The plugin
181
+ uses OpenCode's project directory when available; set `options.cwd` when
182
+ loading the provider directly. Use the `qoder_session_reset` tool to forget
183
+ the mapping. A new session is created automatically if the mapping does not
184
+ exist; existing sessions are resumed through the Qoder SDK.
185
+
186
+ Qoder-native and bridged MCP tools remain provider-owned. If OpenCode supplies
187
+ a function with a colliding native name, the bridge derives a Qoder deny rule
188
+ to avoid executing the same operation in both runtimes.
200
189
 
201
190
  The bridge uses the SDK's safer permission policy by default. To explicitly
202
191
  allow all Qoder tools in a trusted local environment, configure for example:
@@ -218,22 +207,100 @@ Available permission modes are `default`, `acceptEdits`, and
218
207
  `bypassPermissions`. Only explicitly configure `bypassPermissions` when the
219
208
  host environment is trusted.
220
209
 
210
+ Image inputs may reference `file://`, `~/`, or absolute local paths. Only pass
211
+ paths from trusted callers: the bridge bounds image size but does not sandbox
212
+ or restrict readable local files to the project directory. A current turn is
213
+ limited to 64 images and 40 MiB of decoded image data; excess attachments are
214
+ reported as omitted text.
215
+
216
+ ### Plan Mode
217
+
218
+ Plan Mode instructs Qoder to analyze and plan changes without modifying files
219
+ or running action tools. It operates independently from tool permissions,
220
+ preserving your configured `permissionMode`:
221
+
222
+ ```json
223
+ {
224
+ "provider": {
225
+ "qoder": {
226
+ "options": {
227
+ "planMode": true
228
+ }
229
+ }
230
+ }
231
+ }
232
+ ```
233
+
234
+ Run `/qoder_plan_mode` in OpenCode for quick guidance.
235
+
236
+ ### Proxy & Network Routing
237
+
238
+ Pass an outbound proxy URL directly to the Qoder runtime without mutating host
239
+ environment variables (supports `http://`, `https://`, `socks5://`, and `socks://`):
240
+
241
+ ```json
242
+ {
243
+ "provider": {
244
+ "qoder": {
245
+ "options": {
246
+ "proxy": "http://127.0.0.1:8888"
247
+ }
248
+ }
249
+ }
250
+ }
251
+ ```
252
+
253
+ If `proxy` is omitted, the bridge automatically falls back to `HTTPS_PROXY` or
254
+ `HTTP_PROXY` from your environment.
255
+
256
+ ### Skill Evolution
257
+
258
+ Enable autonomous turn-completion skill analysis and recommendations:
259
+
260
+ ```json
261
+ {
262
+ "provider": {
263
+ "qoder": {
264
+ "options": {
265
+ "evolution": {
266
+ "skill": { "mode": "native" }
267
+ }
268
+ }
269
+ }
270
+ }
271
+ }
272
+ ```
273
+
274
+ ### Available Tools
275
+
276
+ The plugin registers several built-in OpenCode tools:
277
+
278
+ - `qoder_usage` — Live account balance, quota percentages, and local cost ledger totals.
279
+ - `qoder_models` — List known Qoder models, context limits, vision/reasoning flags, and multipliers.
280
+ - `qoder_sessions` — List recent Qoder sessions, session IDs, branches, and timestamps via SDK `listSessions()`.
281
+ - `qoder_session_reset` — Forget the persisted Qoder session mapping for the active project.
282
+ - `qoder_plan_mode` — View Plan Mode status and configuration guidance.
283
+
221
284
  ## Troubleshooting
222
285
 
223
286
  | Problem | Solution |
224
287
  |---------|----------|
225
288
  | Auth prompt at startup | Run `qoder login`, then restart opencode |
226
- | `qodercli not found` | Authenticate with `qoder login` or set `QODER_PERSONAL_ACCESS_TOKEN`; the SDK can use its bundled runtime, while an installed CLI is preferred when available |
227
- | Model not found | Verify the model ID matches the table above |
228
- | Missing models in the model list | Run `/qoder_models`; the bridge refreshes the live catalog at every startup and falls back to the last cached catalog plus the built-ins (`lite`, `auto`, `performance`) when offline. If your account serves models in a different Qoder scene, set `QODER_SCENE` before launching opencode (or via provider option `env`) |
289
+ | Qoder runtime unavailable | Authenticate with `qoder login` or set `QODER_PERSONAL_ACCESS_TOKEN`; the bridge uses the SDK's bundled Worker runtime for model discovery and can fall back to an installed CLI automatically |
290
+ | Model not found | Run `opencode models qoder` or `/qoder_models`; model IDs are account- and scene-specific |
291
+ | Missing models in the model list | Restart OpenCode; the bridge performs a live catalog lookup automatically and falls back to the last scoped catalog plus the built-ins (`lite`, `auto`, `performance`) when offline. If your account serves models in a different Qoder scene, set `QODER_SCENE` before launching OpenCode |
229
292
 
230
293
  ### How model discovery works
231
294
 
232
- At startup the bridge immediately exposes cached/built-in models, then
233
- refreshes the live catalog from Qoder (`fetchStrategy: "live"` — the CLI
234
- re-queries the server and falls back to its local cache if the server returns
235
- nothing). Discovered models override built-ins with the same ID; a failed
236
- refresh never removes previously known models.
295
+ At startup the bridge performs a bounded live catalog discovery from Qoder
296
+ before returning the provider configuration (`fetchStrategy: "live"` — the
297
+ bundled Worker runtime re-queries the server, with an automatic installed-CLI
298
+ fallback when necessary). Each successful catalog snapshot replaces
299
+ previously discovered dynamic IDs, so retired models do not remain selectable.
300
+ A failed, empty, or slow refresh falls back to the last scoped catalog and the
301
+ built-ins; no `qodercli --list-models` command or manual model configuration is
302
+ required. The startup wait is bounded to 10 seconds, after which OpenCode
303
+ continues with the available cache/fallbacks.
237
304
 
238
305
  ## Development
239
306
 
@@ -249,7 +316,7 @@ npm run check # full pre-publish verification
249
316
  ### Diagnostics
250
317
 
251
318
  Set `QODER_BRIDGE_DEBUG=1` before launching opencode to emit detailed bridge
252
- logs (model fallbacks, stream aborts, background catalog refreshes, ledger and
319
+ logs (model fallbacks, stream aborts, live catalog discovery, ledger and
253
320
  session-store I/O failures). Warnings that need attention are always printed.
254
321
 
255
322
  State files (usage ledger, session mapping, model cache) live under
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync } from "node:fs";
2
+ import { lstatSync, readFileSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { join } from "node:path";
5
5
 
@@ -8,20 +8,41 @@ import { join } from "node:path";
8
8
  function resolveStateDir(env = process.env) {
9
9
  const override = env.QODER_BRIDGE_STATE_DIR?.trim();
10
10
  if (override) return join(override);
11
- const configHome = env.XDG_CONFIG_HOME?.trim();
11
+ const configHome = env.XDG_CONFIG_HOME?.trim()
12
+ || (process.platform === "win32" ? env.APPDATA?.trim() : undefined);
12
13
  return join(configHome || join(homedir(), ".config"), "opencode-qoder-bridge");
13
14
  }
14
15
 
15
16
  const STATE_FILE = join(resolveStateDir(), "usage.json");
17
+ const MAX_STATE_BYTES = 1_000_000;
16
18
 
17
19
  function fmt(usd) {
18
- return `$${(usd ?? 0).toFixed(4)}`;
20
+ const n = typeof usd === "number" && Number.isFinite(usd) ? usd : 0;
21
+ return `$${n.toFixed(4)}`;
22
+ }
23
+
24
+ function count(value) {
25
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
26
+ }
27
+
28
+ function add(left, right) {
29
+ const total = left + right;
30
+ return Number.isFinite(total) ? total : Number.MAX_SAFE_INTEGER;
19
31
  }
20
32
 
21
33
  function main() {
22
34
  let state = null;
23
35
  try {
36
+ const info = lstatSync(STATE_FILE);
37
+ if (!info.isFile() || info.isSymbolicLink() || info.size > MAX_STATE_BYTES) {
38
+ process.stdout.write("qoder: no usage yet");
39
+ return;
40
+ }
24
41
  state = JSON.parse(readFileSync(STATE_FILE, "utf8"));
42
+ if (!state || typeof state !== "object" || Array.isArray(state)) {
43
+ process.stdout.write("qoder: no usage yet");
44
+ return;
45
+ }
25
46
  } catch {
26
47
  process.stdout.write("qoder: no usage yet");
27
48
  return;
@@ -29,12 +50,15 @@ function main() {
29
50
 
30
51
  const parts = [
31
52
  `cost ${fmt(state.totalCostUsd)}`,
32
- `turns ${state.turnCount ?? 0}`,
33
- `tok ${(state.totalInputTokens ?? 0) + (state.totalOutputTokens ?? 0)}`,
53
+ `turns ${count(state.turnCount)}`,
54
+ `tok ${add(count(state.totalInputTokens), count(state.totalOutputTokens))}`,
34
55
  ];
35
56
 
36
57
  const last = Array.isArray(state.recent) ? state.recent.at(-1) : null;
37
- if (last?.model) parts.push(`last ${last.model}`);
58
+ if (typeof last?.model === "string" && last.model) {
59
+ const model = last.model.replace(/[\u0000-\u001f\u007f]/g, " ").slice(0, 256);
60
+ parts.push(`last ${model}`);
61
+ }
38
62
 
39
63
  process.stdout.write(`qoder: ${parts.join(" · ")}`);
40
64
  }
package/bin/usage.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { getLiveUsage, formatUsageReport } from "../dist/usage.js";
3
3
  import { summarize, formatCost } from "../dist/cost.js";
4
+ import { describeError } from "../dist/logger.js";
4
5
 
5
6
  async function main() {
6
7
  const lines = [];
@@ -14,11 +15,11 @@ async function main() {
14
15
  const summary = summarize();
15
16
  lines.push("");
16
17
  lines.push("Local Usage Ledger");
17
- lines.push(` Reference cost: ${formatCost(summary.totalCostUsd)}`);
18
- lines.push(` Turns: ${summary.turnCount}`);
19
- lines.push(` Tokens: ${summary.totalInputTokens} in / ${summary.totalOutputTokens} out`);
18
+ lines.push(` Reference cost: ${formatCost(summary?.totalCostUsd ?? 0)}`);
19
+ lines.push(` Turns: ${summary?.turnCount ?? 0}`);
20
+ lines.push(` Tokens: ${summary?.totalInputTokens ?? 0} in / ${summary?.totalOutputTokens ?? 0} out`);
20
21
 
21
- const models = Object.entries(summary.byModel);
22
+ const models = Object.entries(summary?.byModel ?? {});
22
23
  if (models.length > 0) {
23
24
  lines.push(" By model:");
24
25
  for (const [name, bucket] of models) {
@@ -30,6 +31,6 @@ async function main() {
30
31
  }
31
32
 
32
33
  main().catch((error) => {
33
- process.stderr.write(`Unable to read Qoder usage: ${error instanceof Error ? error.message : String(error)}\n`);
34
+ process.stderr.write(`Unable to read Qoder usage: ${describeError(error)}\n`);
34
35
  process.exitCode = 1;
35
36
  });
@@ -0,0 +1,8 @@
1
+ /** Race SDK operations against a host-side deadline. Aborting the SDK is
2
+ * still the caller's responsibility; this helper guarantees the bridge does
3
+ * not wait forever for a non-cooperative promise. */
4
+ export declare function withTimeout<T>(operation: PromiseLike<T>, timeoutMs: number, message: string): Promise<T>;
5
+ export declare function closeAsyncIterator(iterator: {
6
+ return?: (value?: void | PromiseLike<void>) => PromiseLike<unknown> | unknown;
7
+ }, timeoutMs: number): Promise<void>;
8
+ //# sourceMappingURL=async-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-utils.d.ts","sourceRoot":"","sources":["../src/async-utils.ts"],"names":[],"mappings":"AAAA;;qDAEqD;AACrD,wBAAgB,WAAW,CAAC,CAAC,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CASxG;AAED,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE;IAAE,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,WAAW,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;CAAE,EAC3F,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAQf"}
@@ -0,0 +1,27 @@
1
+ /** Race SDK operations against a host-side deadline. Aborting the SDK is
2
+ * still the caller's responsibility; this helper guarantees the bridge does
3
+ * not wait forever for a non-cooperative promise. */
4
+ export function withTimeout(operation, timeoutMs, message) {
5
+ let timer;
6
+ const timeout = new Promise((_, reject) => {
7
+ timer = setTimeout(() => reject(new Error(message)), timeoutMs);
8
+ if (typeof timer.unref === "function")
9
+ timer.unref();
10
+ });
11
+ return Promise.race([Promise.resolve(operation), timeout]).finally(() => {
12
+ if (timer)
13
+ clearTimeout(timer);
14
+ });
15
+ }
16
+ export async function closeAsyncIterator(iterator, timeoutMs) {
17
+ if (typeof iterator.return !== "function")
18
+ return;
19
+ const closing = Promise.resolve().then(() => iterator.return?.(undefined)).then(() => undefined);
20
+ try {
21
+ await withTimeout(closing, timeoutMs, "SDK iterator cleanup timed out");
22
+ }
23
+ catch {
24
+ // Cleanup is best effort; the request's primary error remains authoritative.
25
+ }
26
+ }
27
+ //# sourceMappingURL=async-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-utils.js","sourceRoot":"","sources":["../src/async-utils.ts"],"names":[],"mappings":"AAAA;;qDAEqD;AACrD,MAAM,UAAU,WAAW,CAAI,SAAyB,EAAE,SAAiB,EAAE,OAAe;IAC1F,IAAI,KAAgD,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;QAC/C,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QAChE,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,UAAU;YAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IACvD,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;QACtE,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,QAA2F,EAC3F,SAAiB;IAEjB,IAAI,OAAO,QAAQ,CAAC,MAAM,KAAK,UAAU;QAAE,OAAO;IAClD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACjG,IAAI,CAAC;QACH,MAAM,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,gCAAgC,CAAC,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,6EAA6E;IAC/E,CAAC;AACH,CAAC"}
package/dist/auth.d.ts CHANGED
@@ -7,5 +7,6 @@ export declare function isAuthenticated(): boolean;
7
7
  * Returns null when no separately installed CLI is found. The Qoder SDK can
8
8
  * still run through its bundled Worker runtime when credentials are present.
9
9
  */
10
- export declare function findQoderCLI(): string | null;
10
+ export declare function findQoderCLI(force?: boolean): string | null;
11
+ export declare function resetCachedCliPath(): void;
11
12
  //# sourceMappingURL=auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AASA,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAuBD;;;;;;;GAOG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAI5C"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AASA,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAuBD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,UAAQ,GAAG,MAAM,GAAG,IAAI,CAIzD;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
package/dist/auth.js CHANGED
@@ -38,12 +38,15 @@ let cachedCliPath;
38
38
  * Returns null when no separately installed CLI is found. The Qoder SDK can
39
39
  * still run through its bundled Worker runtime when credentials are present.
40
40
  */
41
- export function findQoderCLI() {
42
- if (cachedCliPath !== undefined)
41
+ export function findQoderCLI(force = false) {
42
+ if (!force && cachedCliPath !== undefined)
43
43
  return cachedCliPath;
44
44
  cachedCliPath = resolveCli();
45
45
  return cachedCliPath;
46
46
  }
47
+ export function resetCachedCliPath() {
48
+ cachedCliPath = undefined;
49
+ }
47
50
  function resolveCli() {
48
51
  const exe = process.platform === "win32" ? "qodercli.exe" : "qodercli";
49
52
  for (const dir of (process.env.PATH ?? "").split(delimiter)) {
@@ -58,12 +61,12 @@ function resolveCli() {
58
61
  return local;
59
62
  const binDir = join(homedir(), ".qoder", "bin", "qodercli");
60
63
  try {
61
- const latest = readdirSync(binDir)
64
+ const versions = readdirSync(binDir)
62
65
  .filter((f) => f.startsWith("qodercli-"))
63
- .sort()
64
- .at(-1);
65
- if (latest) {
66
- const p = join(binDir, latest);
66
+ .sort(compareCliVersions)
67
+ .reverse();
68
+ for (const version of versions) {
69
+ const p = join(binDir, version);
67
70
  if (isExecutableFile(p))
68
71
  return p;
69
72
  }
@@ -73,4 +76,20 @@ function resolveCli() {
73
76
  }
74
77
  return null;
75
78
  }
79
+ function compareCliVersions(left, right) {
80
+ const leftParts = left.slice("qodercli-".length).split(/[.-]/);
81
+ const rightParts = right.slice("qodercli-".length).split(/[.-]/);
82
+ const length = Math.max(leftParts.length, rightParts.length);
83
+ for (let i = 0; i < length; i++) {
84
+ const a = leftParts[i] ?? "";
85
+ const b = rightParts[i] ?? "";
86
+ const aNumber = /^\d+$/.test(a) ? Number(a) : NaN;
87
+ const bNumber = /^\d+$/.test(b) ? Number(b) : NaN;
88
+ if (Number.isFinite(aNumber) && Number.isFinite(bNumber) && aNumber !== bNumber)
89
+ return aNumber - bNumber;
90
+ if (a !== b)
91
+ return a.localeCompare(b);
92
+ }
93
+ return left.localeCompare(right);
94
+ }
76
95
  //# sourceMappingURL=auth.js.map
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
package/dist/cost.d.ts CHANGED
@@ -23,9 +23,10 @@ interface PersistedState {
23
23
  }>;
24
24
  recent: TurnCost[];
25
25
  }
26
+ export declare function flushLedgerSync(): void;
26
27
  export interface RecordInput {
27
28
  model: string;
28
- usage: {
29
+ usage?: {
29
30
  input_tokens?: number | null;
30
31
  output_tokens?: number | null;
31
32
  cache_read_input_tokens?: number | null;
@@ -1 +1 @@
1
- {"version":3,"file":"cost.d.ts","sourceRoot":"","sources":["../src/cost.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAS5D,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,UAAU,cAAc;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvG,MAAM,EAAE,QAAQ,EAAE,CAAC;CACpB;AAqJD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE;QACL,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxC,2BAA2B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC7C,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACzC;AAED,0DAA0D;AAC1D,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,QAAQ,CA+BvD;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACnC,MAAM,EAAE,QAAQ,EAAE,CAAC;CACpB;AAED,wBAAgB,SAAS,IAAI,YAAY,CAUxC;AAED,wBAAgB,WAAW,IAAI,IAAI,CAGlC;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C"}
1
+ {"version":3,"file":"cost.d.ts","sourceRoot":"","sources":["../src/cost.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAa5D,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,UAAU,cAAc;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvG,MAAM,EAAE,QAAQ,EAAE,CAAC;CACpB;AAoND,wBAAgB,eAAe,IAAI,IAAI,CAOtC;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QACN,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxC,2BAA2B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC7C,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACzC;AAED,0DAA0D;AAC1D,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,QAAQ,CAoBvD;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACnC,MAAM,EAAE,QAAQ,EAAE,CAAC;CACpB;AAED,wBAAgB,SAAS,IAAI,YAAY,CAUxC;AAED,wBAAgB,WAAW,IAAI,IAAI,CAKlC;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C"}