pi-commandcode-provider 0.4.3 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.5.1 - 2026-08-11
6
+
7
+ - Add model-specific image input capabilities from the `command-code@1.15.1` catalog and forward user and tool-result images using the current Command Code wire format.
8
+ - Update the Command Code client version header to `1.15.1`.
9
+
10
+ ### Contributors
11
+
12
+ - @DiyarD — reported missing vision support for GPT-5.6 Luna, Muse Spark 1.2, and other vision-capable models.
13
+
14
+ ## 0.5.0 - 2026-08-07
15
+
16
+ - Stop replaying completed assistant reasoning traces to Command Code while preserving visible text and completed tool calls in follow-up request history.
17
+ - Add `/commandcode-refresh` and `/commandcode-status` commands for safe model-catalog refreshes and redacted diagnostics.
18
+ - Bound model discovery to a configurable 10-second timeout so a slow Provider API cannot block pi startup; timed-out discovery uses the validated cache when available.
19
+ - Normalize Command Code context overflow failures so pi can auto-compact and retry, while leaving unrelated rate-limit and capacity errors unchanged.
20
+ - Keep the legacy `/alpha/generate` integration explicitly text-only: image input and image tool results are rejected instead of being silently dropped, and models do not claim image capability until the protocol exposes documented support and limits.
21
+ - Replace blanket reasoning metadata with model-specific Command Code effort support. Known models expose a `thinkingLevelMap`, and selected supported Pi levels are forwarded as `params.reasoning_effort`; unsupported or unknown models do not receive reasoning request fields.
22
+ - Add repository commands for testing the current checkout either in a logged-out, automatically cleaned-up pi environment or with existing credentials and only Command Code models enabled.
23
+ - Refresh display pricing for the current Command Code model catalog, remove expired Qwen promotional rates, add current free and discounted models, and require review when temporary prices expire.
24
+ - Use the host-provided `pi-ai` and `pi-coding-agent` core packages instead of installing private runtime copies, including for local and out-of-store development checkouts.
25
+ - Fix cached input tokens being counted twice.
26
+
27
+ ### Contributors
28
+
29
+ - @IfkumRfnl — fixed cached input token accounting.
30
+
3
31
  ## 0.4.3 - 2026-08-02
4
32
 
5
33
  - Allow pi to start when model discovery is unavailable. The provider now caches the last successfully fetched model catalog so previously discovered Command Code models remain selectable offline; a first offline start without a cache keeps Command Code unavailable until `/reload` succeeds.
package/CONTRIBUTING.md CHANGED
@@ -21,9 +21,27 @@ npm run test:models
21
21
  npm run test:oauth
22
22
  npm run test:abort
23
23
  npm run test:stream
24
+ npm run test:pi-isolated
25
+ npm run test:pi-authenticated
24
26
  npm run test:pi-local
25
27
  ```
26
28
 
29
+ Start an isolated pi instance with only the current checkout installed and no existing Command Code credentials:
30
+
31
+ ```sh
32
+ npm run pi:isolated
33
+ ```
34
+
35
+ Run `/login` inside pi. Temporary credentials, configuration, and sessions are deleted when pi exits.
36
+
37
+ Start the current checkout with your existing pi credentials and only Command Code models in the model picker:
38
+
39
+ ```sh
40
+ npm run pi:authenticated
41
+ ```
42
+
43
+ Both commands accept additional pi arguments after `--`, for example `npm run pi:authenticated -- --model claude-sonnet-4-6`.
44
+
27
45
  Before opening a PR, run:
28
46
 
29
47
  ```sh
package/README.md CHANGED
@@ -1,22 +1,11 @@
1
1
  # pi-commandcode-provider
2
2
 
3
- A [pi](https://github.com/badlogic/pi-mono) custom provider that connects pi to the [Command Code](https://commandcode.ai) API.
3
+ [![CI](https://github.com/patlux/pi-commandcode-provider/actions/workflows/ci.yml/badge.svg)](https://github.com/patlux/pi-commandcode-provider/actions/workflows/ci.yml)
4
+ [![Memory benchmark](https://github.com/patlux/pi-commandcode-provider/actions/workflows/memory-benchmark.yml/badge.svg)](https://github.com/patlux/pi-commandcode-provider/actions/workflows/memory-benchmark.yml)
4
5
 
5
- > **Disclaimer:** This is an unofficial, community-maintained package. I am not affiliated with, endorsed by, or connected to Command Code in any way. This provider simply forwards requests to the public Command Code API using your own API key.
6
+ A custom provider for [pi](https://github.com/earendil-works/pi) that connects to the [Command Code](https://commandcode.ai) Provider API.
6
7
 
7
- > **Note:** This package only provides a model _provider_. It does **not** include an API key. You must bring your own Command Code API key or subscription.
8
-
9
- > 💰 **Current offers:** Command Code offers [4× usage of DeepSeek V4 Pro](https://commandcode.ai/docs/resources/pricing-limits#deepseek-v4-pro-4x-usage) and [2× usage of Qwen 3.7 Max](https://commandcode.ai/docs/resources/pricing-limits#qwen-3.7-max-2x-usage).
10
-
11
- ## Models
12
-
13
- Models are fetched live from Command Code's Provider API at startup, so new models like Qwen 3.7 Max show up without a package release.
14
-
15
- You can list the current Command Code models with:
16
-
17
- ```sh
18
- pi -e index.ts --list-models
19
- ```
8
+ > **Disclaimer:** This is an unofficial, community-maintained integration. It is not affiliated with, endorsed by, or supported by Command Code. You need your own Command Code account and API key or subscription. Command Code's terms, availability, and pricing apply.
20
9
 
21
10
  ## Install
22
11
 
@@ -24,59 +13,49 @@ pi -e index.ts --list-models
24
13
  pi install npm:pi-commandcode-provider
25
14
  ```
26
15
 
27
- Or shorthand:
16
+ Start or reload pi, then authenticate:
28
17
 
29
- ```sh
30
- pi install pi-commandcode-provider
18
+ ```txt
19
+ /login
31
20
  ```
32
21
 
33
- Then reload pi:
22
+ Select **Use a subscription**, then **Command Code**. Complete the browser flow and choose a model with `/model`.
34
23
 
35
- ```txt
36
- /reload
37
- ```
24
+ ## Oh My Pi
38
25
 
39
- ### Oh My Pi
26
+ Install the same package in [Oh My Pi](https://github.com/can1357/oh-my-pi):
40
27
 
41
28
  ```sh
42
29
  omp plugin install pi-commandcode-provider
43
30
  ```
44
31
 
45
- Then restart OMP or run:
46
-
47
- ```txt
48
- /reload
49
- ```
50
-
51
- ## Setup
52
-
53
- Set your Command Code API key using one of these methods:
54
-
55
- ### 1. Browser login (recommended)
56
-
57
- In pi, run:
32
+ Restart OMP or run `/reload`, then use `/login` and select **Use a subscription** followed by **Command Code**.
58
33
 
59
- ```txt
60
- /login
61
- ```
34
+ ## Authentication
62
35
 
63
- Then select **Command Code** from the provider list.
36
+ ### Browser login
64
37
 
65
- <img width="1520" height="554" alt="image" src="https://github.com/user-attachments/assets/071e929a-6f49-4803-bfec-7a31368fb12a" />
38
+ Run `/login` in pi or OMP. Select **Use a subscription**, then **Command Code**. The browser flow stores the returned credential in the host's auth file.
66
39
 
67
- This opens Command Code in your browser and stores the returned API key in pi's auth file. If the browser shows "Copy your API key" because automatic transfer failed, copy that key and paste it into the pi terminal prompt.
40
+ <img width="1520" height="554" alt="Select Command Code in pi's login dialog" src="https://github.com/user-attachments/assets/071e929a-6f49-4803-bfec-7a31368fb12a" />
68
41
 
69
- > Note: `/login commandcode` is not supported by pi currently; use interactive `/login` and select Command Code.
42
+ If automatic transfer from the browser fails, copy the API key shown by Command Code and paste it into the terminal prompt.
70
43
 
71
- ### 2. Environment variable
44
+ ### Environment variable
72
45
 
73
46
  ```sh
74
47
  export COMMANDCODE_API_KEY="user_..."
75
48
  ```
76
49
 
77
- ### 3. Auth file
50
+ ### Auth file
51
+
52
+ The provider also reads existing credentials from:
78
53
 
79
- Create `~/.commandcode/auth.json`:
54
+ - `~/.commandcode/auth.json`
55
+ - `~/.pi/agent/auth.json`
56
+ - `~/.omp/agent/auth.json`
57
+
58
+ Supported examples:
80
59
 
81
60
  ```json
82
61
  {
@@ -84,8 +63,6 @@ Create `~/.commandcode/auth.json`:
84
63
  }
85
64
  ```
86
65
 
87
- The official Command Code CLI auth shape is also supported:
88
-
89
66
  ```json
90
67
  {
91
68
  "command-code": {
@@ -95,8 +72,6 @@ The official Command Code CLI auth shape is also supported:
95
72
  }
96
73
  ```
97
74
 
98
- Or use a pi/OMP auth file at `~/.pi/agent/auth.json` or `~/.omp/agent/auth.json`:
99
-
100
75
  ```json
101
76
  {
102
77
  "commandcode": "user_..."
@@ -105,55 +80,108 @@ Or use a pi/OMP auth file at `~/.pi/agent/auth.json` or `~/.omp/agent/auth.json`
105
80
 
106
81
  ## Usage
107
82
 
108
- After installing and setting your API key, select a Command Code model in pi:
83
+ Open `/model` and select one of the models provided by Command Code. Model availability changes over time and is refreshed from the Provider API when the extension loads.
109
84
 
110
- ```txt
111
- /model deepseek/deepseek-v4-flash
85
+ ### Reasoning support
86
+
87
+ Reasoning metadata is enriched only for models whose Command Code effort support is known. Those models register a model-specific `thinkingLevelMap`, so pi and OMP expose only supported levels. A selected supported level is sent as the documented `params.reasoning_effort` field; `off`, unsupported levels, and newly discovered models without metadata do not add reasoning fields to the request. No prompt instructions are injected.
88
+
89
+ Reasoning blocks from completed assistant turns remain visible in pi's local session, but are not replayed to Command Code in later requests. Only the assistant's user-visible text and completed tool calls are sent back as history. This matches the current Command Code CLI behavior and prevents prior private reasoning traces from interfering with reasoning on follow-up turns.
90
+
91
+ List Command Code models from the terminal:
92
+
93
+ ```sh
94
+ pi --list-models commandcode
112
95
  ```
113
96
 
114
- Any query will then use the Command Code API. You can list available models:
97
+ In OMP, use:
115
98
 
116
99
  ```sh
117
- pi -e index.ts --list-models # or /models within pi
118
- omp -e index.ts --list-models
100
+ omp models
119
101
  ```
120
102
 
121
- In OMP, use the provider-qualified model name:
103
+ For non-interactive OMP requests, use a provider-qualified model ID shown by `omp models`. For example:
122
104
 
123
105
  ```sh
124
106
  omp -p "hello" --model commandcode/deepseek/deepseek-v4-flash
125
107
  ```
126
108
 
127
- OMP currently resolves `--provider commandcode --model ...` before extension providers are loaded, so prefer `--model commandcode/<model-id>`. <!-- TODO: remove this note once OMP fixes provider resolution order for extension-loaded providers -->
109
+ ## Model discovery and offline behavior
128
110
 
129
- ## Model discovery
130
-
131
- On startup, the provider fetches:
111
+ The provider fetches the current model catalog from:
132
112
 
133
113
  ```txt
134
114
  https://api.commandcode.ai/provider/v1/models
135
115
  ```
136
116
 
137
- The last successfully fetched catalog is cached at `<agent-dir>/commandcode-models.json` (`~/.pi/agent/commandcode-models.json` by default). The agent directory follows pi's `PI_CODING_AGENT_DIR` setting, so compatible hosts such as OMP keep the cache in their own agent directory. If model discovery is temporarily unavailable, the provider uses this cached catalog so previously discovered Command Code models remain selectable. On a first offline start without a cache, pi still loads, but Command Code models remain unavailable until the connection is restored and `/reload` succeeds.
117
+ The last successful catalog is cached at `<agent-dir>/commandcode-models.json`. For pi this is `~/.pi/agent/commandcode-models.json` by default. Compatible hosts such as OMP use their own agent directory.
118
+
119
+ If the endpoint is temporarily unavailable, the provider uses the cached catalog. On a first offline start without a cache, pi still loads, but Command Code models remain unavailable until the connection is restored and `/commandcode-refresh` succeeds.
120
+
121
+ While pi is running, use these provider commands without restarting:
122
+
123
+ - `/commandcode-refresh` fetches and re-registers the current model catalog. Overlapping refreshes are coalesced, and a failed refresh keeps the last valid catalog active.
124
+ - `/commandcode-status` shows redacted discovery diagnostics, including the source, model count, timestamps, cache path, endpoint, and warning.
125
+
126
+ The following environment variables are intended for tests, local mocks, and compatible API endpoints:
127
+
128
+ - `COMMANDCODE_API_BASE`
129
+ - `COMMANDCODE_MODELS_URL`
130
+ - `COMMANDCODE_MODELS_CACHE`
131
+ - `COMMANDCODE_MODELS_TIMEOUT_MS` (defaults to 10 seconds; invalid or non-positive values use the default)
132
+
133
+ ## Image input
134
+
135
+ The provider advertises image input only for models marked with the `image` input modality in the official Command Code CLI model catalog. The capability snapshot currently follows `command-code@1.15.1`; unknown models default to text-only until their upstream metadata is reviewed.
136
+
137
+ For vision-capable models, image blocks from user messages and tool results are forwarded in Command Code's current data-URL wire format. Text-only models reject image content before making a network request instead of silently dropping it.
138
+
139
+ ## Pricing display
140
+
141
+ The Command Code Provider API does not currently include prices in its model catalog. This extension therefore keeps a static table for models with known prices so pi can display estimated request costs.
142
+
143
+ Models missing from that table display zero cost in pi. This does **not** mean that Command Code will bill the request at zero. Check the current [Command Code pricing](https://commandcode.ai/docs/resources/pricing-limits) before relying on the displayed value.
144
+
145
+ ## Update and remove
146
+
147
+ Update installed pi packages:
148
+
149
+ ```sh
150
+ pi update --extensions
151
+ ```
152
+
153
+ Remove the provider:
138
154
 
139
- For tests or local mocks, override the endpoint with `COMMANDCODE_MODELS_URL` and the cache file with `COMMANDCODE_MODELS_CACHE`.
155
+ ```sh
156
+ pi remove npm:pi-commandcode-provider
157
+ ```
140
158
 
141
- ## Pricing
159
+ For OMP:
142
160
 
143
- Command Code does not yet expose model pricing through its Provider API. The provider ships a static cost table (`MODEL_COSTS` in `index.ts`) for known models so that pi can display per-model pricing.
161
+ ```sh
162
+ omp plugin upgrade pi-commandcode-provider
163
+ omp plugin uninstall pi-commandcode-provider
164
+ ```
165
+
166
+ ## Development
144
167
 
145
- - Models present in `MODEL_COSTS` show their real per-million-token rates (including promotional deals like the DeepSeek V4 Pro 4× discount and Qwen 3.7 Max discount).
146
- - Models **not** in the table fall back to zero cost. When the Provider API adds a `cost` field, the static table can be removed.
168
+ Start an isolated pi instance with only the current checkout installed and no existing Command Code credentials:
147
169
 
148
- To add or update a price, edit the `MODEL_COSTS` record in `index.ts` and update the corresponding test in `tests/test-pricing.ts`.
170
+ ```sh
171
+ npm run pi:isolated
172
+ ```
149
173
 
150
- ## Contributing
174
+ Run `/login` inside pi. Temporary credentials, configuration, and sessions are deleted when pi exits.
151
175
 
152
- See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, PR expectations, and commit message rules.
176
+ Start the current checkout with your existing pi credentials and only Command Code models in the model picker:
177
+
178
+ ```sh
179
+ npm run pi:authenticated
180
+ ```
153
181
 
154
- ## Release
182
+ Both commands accept additional pi arguments after `--`, for example `npm run pi:authenticated -- --model claude-sonnet-4-6`.
155
183
 
156
- See [RELEASE.md](RELEASE.md) for the prerelease, npm smoke-test, stable publish, git tag, and GitHub follow-up checklist.
184
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for local setup and tests. See [RELEASE.md](RELEASE.md) for the release process.
157
185
 
158
186
  ## License
159
187
 
package/index.ts CHANGED
@@ -2,96 +2,45 @@
2
2
  * Command Code provider for pi.
3
3
  *
4
4
  * Connects pi to Command Code's API (https://api.commandcode.ai/alpha/generate).
5
- *
6
- * Authentication (pick one):
7
- * 1. Run `/login`, then select Command Code — opens browser to commandcode.ai, auto-stores API key
8
- * 2. Set COMMANDCODE_API_KEY environment variable
9
- * 3. Place API key in `~/.commandcode/auth.json` or `~/.pi/agent/auth.json`
10
- * as {"apiKey": "user_..."} or {"commandcode": "user_..."}
11
- *
12
- * Models are fetched from Command Code's Provider API at startup.
5
+ * The provider uses pi's legacy extension registration surface because the
6
+ * current pi host exposes `registerProvider(name, config)`, including OMP.
13
7
  */
14
8
 
15
9
  import { AssistantMessageEventStream } from "@earendil-works/pi-ai"
16
- import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent"
10
+ import {
11
+ getAgentDir,
12
+ type ExtensionAPI,
13
+ type ExtensionCommandContext,
14
+ type ProviderConfig,
15
+ } from "@earendil-works/pi-coding-agent"
17
16
  import { join } from "node:path"
18
17
 
19
18
  import { COMMAND_CODE_CLI_VERSION, createStreamCommandCode, DEFAULT_API_BASE } from "./src/core.ts"
20
19
  import { calculateCommandCodeCost } from "./src/cost.ts"
21
- import { DEFAULT_MODELS_URL, loadCommandCodeModels } from "./src/models.ts"
22
- import { getApiKey, login, refreshToken } from "./src/oauth.ts"
23
-
24
- const API_BASE = process.env.COMMANDCODE_API_BASE ?? DEFAULT_API_BASE
25
- const MODELS_URL = process.env.COMMANDCODE_MODELS_URL ?? DEFAULT_MODELS_URL
26
- const MODELS_CACHE_PATH =
27
- process.env.COMMANDCODE_MODELS_CACHE ?? join(getAgentDir(), "commandcode-models.json")
28
-
29
- type CommandCodeModelCost = {
30
- input: number
31
- output: number
32
- cacheRead: number
33
- cacheWrite: number
34
- }
35
-
36
- const ZERO_MODEL_COST: CommandCodeModelCost = {
37
- input: 0,
38
- output: 0,
39
- cacheRead: 0,
40
- cacheWrite: 0,
41
- }
20
+ import {
21
+ DEFAULT_MODELS_URL,
22
+ getModelsTimeoutMs,
23
+ inputModalitiesForModel,
24
+ loadCommandCodeModels,
25
+ thinkingMetadataForModel,
26
+ type CommandCodeModel,
27
+ } from "./src/models.ts"
28
+ import { getApiKey as getOAuthApiKey, login, refreshToken } from "./src/oauth.ts"
29
+ import { MODEL_COSTS, ZERO_MODEL_COST } from "./src/pricing.ts"
30
+ import { createCommandCodeRuntime } from "./src/runtime.ts"
31
+ import { normalizeCommandCodeMessage } from "./src/overflow.ts"
42
32
 
43
- // The Provider API supplies the current model list. Keep known display pricing
44
- // here until the Provider API exposes prices directly.
45
- const MODEL_COSTS: Record<string, CommandCodeModelCost> = {
46
- "claude-opus-4-7": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
47
- "claude-opus-4-6": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
48
- "claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
49
- "claude-haiku-4-5-20251001": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
50
- "gpt-5.5": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
51
- "gpt-5.4": { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 },
52
- "gpt-5.3-codex": { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 0 },
53
- "gpt-5.4-mini": { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
54
- "google/gemini-3.5-flash": { input: 1.5, output: 9, cacheRead: 0.15, cacheWrite: 0 },
55
- "google/gemini-3.1-flash-lite": { input: 0.25, output: 1.5, cacheRead: 0.03, cacheWrite: 0 },
56
- // 4× usage deal: 75% off (permanent, no expiry)
57
- "deepseek/deepseek-v4-pro": { input: 0.435, output: 0.87, cacheRead: 0.003625, cacheWrite: 0 },
58
- "deepseek/deepseek-v4-flash": { input: 0.14, output: 0.28, cacheRead: 0.028, cacheWrite: 0 },
59
- "moonshotai/Kimi-K2.6": { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0 },
60
- "moonshotai/Kimi-K2.5": { input: 0.6, output: 3, cacheRead: 0.1, cacheWrite: 0 },
61
- "zai-org/GLM-5.1": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
62
- "zai-org/GLM-5": { input: 1, output: 3.2, cacheRead: 0.2, cacheWrite: 0 },
63
- "MiniMaxAI/MiniMax-M2.7": { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
64
- "MiniMaxAI/MiniMax-M2.5": { input: 0.27, output: 0.95, cacheRead: 0.03, cacheWrite: 0 },
65
- "Qwen/Qwen3.6-Max-Preview": { input: 1.3, output: 7.8, cacheRead: 0.26, cacheWrite: 1.63 },
66
- "Qwen/Qwen3.6-Plus": { input: 0.5, output: 3, cacheRead: 0.1, cacheWrite: 0 },
67
- // 2× usage deal: 50% off through June 22, 2026
68
- "Qwen/Qwen3.7-Max": { input: 1.25, output: 3.75, cacheRead: 0.25, cacheWrite: 1.56 },
69
- "stepfun/Step-3.5-Flash": { input: 0.1, output: 0.3, cacheRead: 0.02, cacheWrite: 0 },
70
- "xiaomi/mimo-v2.5-pro": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
71
- "xiaomi/mimo-v2.5": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
72
- }
73
-
74
- const streamCommandCode = createStreamCommandCode({
75
- createStream: () => new AssistantMessageEventStream(),
76
- calculateCost: calculateCommandCodeCost,
77
- apiBase: API_BASE,
78
- })
79
-
80
- // ---------------------------------------------------------------------------
81
- // Extension entry point
82
- // ---------------------------------------------------------------------------
83
-
84
- export default async function (pi: ExtensionAPI) {
85
- const { models, warning } = await loadCommandCodeModels({
86
- url: MODELS_URL,
87
- cachePath: MODELS_CACHE_PATH,
88
- })
89
-
90
- if (warning) console.warn(`[commandcode] ${warning}`)
91
-
92
- pi.registerProvider("commandcode", {
33
+ function createProviderConfig(
34
+ models: readonly CommandCodeModel[],
35
+ apiBase: string,
36
+ streamCommandCode: ProviderConfig["streamSimple"],
37
+ ): ProviderConfig {
38
+ return {
93
39
  name: "Command Code",
94
- baseUrl: API_BASE,
40
+ baseUrl: apiBase,
41
+ // Keep environment authentication dynamic. OAuth credentials are resolved
42
+ // by pi's oauth registration, while the custom stream retains its own
43
+ // request-time legacy-file fallback for older compatible hosts.
95
44
  apiKey: "$COMMANDCODE_API_KEY",
96
45
  authHeader: true,
97
46
  api: "commandcode-custom",
@@ -104,16 +53,60 @@ export default async function (pi: ExtensionAPI) {
104
53
  name: "Command Code",
105
54
  login,
106
55
  refreshToken,
107
- getApiKey,
56
+ getApiKey: getOAuthApiKey,
108
57
  },
109
- models: models.map((model) => ({
110
- id: model.id,
111
- name: model.name,
112
- reasoning: model.reasoning,
113
- input: ["text"] as const,
114
- cost: MODEL_COSTS[model.id] ?? ZERO_MODEL_COST,
115
- contextWindow: model.contextWindow,
116
- maxTokens: model.maxTokens,
117
- })),
58
+ models: models.map(createProviderModel),
59
+ }
60
+ }
61
+
62
+ function createProviderModel(model: {
63
+ id: string
64
+ name: string
65
+ reasoning: boolean
66
+ contextWindow: number
67
+ maxTokens: number
68
+ }) {
69
+ return {
70
+ id: model.id,
71
+ name: model.name,
72
+ reasoning: model.reasoning,
73
+ ...(thinkingMetadataForModel(model.id) ?? {}),
74
+ input: inputModalitiesForModel(model.id),
75
+ cost: MODEL_COSTS[model.id] ?? ZERO_MODEL_COST,
76
+ contextWindow: model.contextWindow,
77
+ maxTokens: model.maxTokens,
78
+ } as const
79
+ }
80
+
81
+ export default async function (pi: ExtensionAPI) {
82
+ const apiBase = process.env.COMMANDCODE_API_BASE ?? DEFAULT_API_BASE
83
+ const modelsUrl = process.env.COMMANDCODE_MODELS_URL ?? DEFAULT_MODELS_URL
84
+ const modelsTimeoutMs = getModelsTimeoutMs()
85
+ const modelsCachePath =
86
+ process.env.COMMANDCODE_MODELS_CACHE ?? join(getAgentDir(), "commandcode-models.json")
87
+ const streamCommandCode = createStreamCommandCode({
88
+ createStream: () => new AssistantMessageEventStream(),
89
+ calculateCost: calculateCommandCodeCost,
90
+ apiBase,
91
+ })
92
+
93
+ pi.on("message_end", async (event, ctx) => {
94
+ if (event.message.role !== "assistant") return
95
+ const normalized = normalizeCommandCodeMessage(event.message, ctx.model?.provider)
96
+ return normalized ? { message: normalized.message } : undefined
118
97
  })
98
+
99
+ const runtime = createCommandCodeRuntime<ProviderConfig, ExtensionCommandContext>(pi, {
100
+ endpoint: modelsUrl,
101
+ cachePath: modelsCachePath,
102
+ loadModels: () =>
103
+ loadCommandCodeModels({
104
+ url: modelsUrl,
105
+ cachePath: modelsCachePath,
106
+ timeoutMs: modelsTimeoutMs,
107
+ }),
108
+ createProviderConfig: (models) => createProviderConfig(models, apiBase, streamCommandCode),
109
+ })
110
+
111
+ await runtime.initialize()
119
112
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-commandcode-provider",
3
- "version": "0.4.3",
3
+ "version": "0.5.1",
4
4
  "description": "pi custom provider for Command Code API (commandcode.ai)",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -21,6 +21,7 @@
21
21
  "files": [
22
22
  "index.ts",
23
23
  "src/",
24
+ "scripts/",
24
25
  "README.md",
25
26
  "CHANGELOG.md",
26
27
  "CONTRIBUTING.md",
@@ -28,19 +29,26 @@
28
29
  "LICENSE"
29
30
  ],
30
31
  "scripts": {
31
- "test": "npm run typecheck && tsx tests/test-pure-functions.ts && tsx tests/test-models.ts && tsx tests/test-pricing.ts && tsx tests/test-cost.ts && tsx tests/test-oauth.ts && tsx tests/test-abort.ts && tsx tests/test-stream.ts && tsx tests/test-retry.ts && node tests/test-pi-local.mjs && node tests/test-omp-compat.mjs",
32
+ "test": "npm run typecheck && tsx tests/test-package-manifest.ts && tsx tests/test-pure-functions.ts && tsx tests/test-models.ts && tsx tests/test-runtime.ts && tsx tests/test-pricing.ts && tsx tests/test-cost.ts && tsx tests/test-oauth.ts && tsx tests/test-abort.ts && tsx tests/test-overflow.ts && tsx tests/test-stream.ts && tsx tests/test-retry.ts && node tests/test-pi-isolated.mjs && node tests/test-pi-authenticated.mjs && node tests/test-pi-local.mjs && node tests/test-omp-compat.mjs",
32
33
  "typecheck": "tsc --noEmit",
33
34
  "format:check": "prettier --check '**/*.{ts,mjs,json,md}'",
34
35
  "format": "prettier --write '**/*.{ts,mjs,json,md}'",
36
+ "pi:isolated": "node scripts/pi-isolated.mjs",
37
+ "pi:authenticated": "node scripts/pi-authenticated.mjs",
35
38
  "test:unit": "tsx tests/test-pure-functions.ts",
36
39
  "test:models": "tsx tests/test-models.ts",
40
+ "test:runtime": "tsx tests/test-runtime.ts",
37
41
  "test:pricing": "tsx tests/test-pricing.ts",
38
42
  "test:oauth": "tsx tests/test-oauth.ts",
39
43
  "test:abort": "tsx tests/test-abort.ts",
44
+ "test:overflow": "tsx tests/test-overflow.ts",
40
45
  "test:stream": "tsx tests/test-stream.ts",
41
46
  "test:retry": "tsx tests/test-retry.ts",
47
+ "test:pi-isolated": "node tests/test-pi-isolated.mjs",
48
+ "test:pi-authenticated": "node tests/test-pi-authenticated.mjs",
42
49
  "test:pi-local": "node tests/test-pi-local.mjs",
43
50
  "test:smoke": "node tests/test-smoke.mjs",
51
+ "test:e2e:live": "node tests/test-live-e2e.mjs",
44
52
  "test:cost": "tsx tests/test-cost.ts"
45
53
  },
46
54
  "pi": {
@@ -54,13 +62,14 @@
54
62
  "tsx": "4.22.4",
55
63
  "typescript": "6.0.3"
56
64
  },
57
- "dependencies": {
58
- "@earendil-works/pi-ai": "0.75.5"
59
- },
60
65
  "peerDependencies": {
61
- "@earendil-works/pi-coding-agent": "^0.75.5"
66
+ "@earendil-works/pi-ai": "*",
67
+ "@earendil-works/pi-coding-agent": "*"
62
68
  },
63
69
  "peerDependenciesMeta": {
70
+ "@earendil-works/pi-ai": {
71
+ "optional": true
72
+ },
64
73
  "@earendil-works/pi-coding-agent": {
65
74
  "optional": true
66
75
  }
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from "node:child_process"
4
+ import { dirname, resolve } from "node:path"
5
+ import { fileURLToPath } from "node:url"
6
+
7
+ const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..")
8
+ const extensionPath = resolve(repoRoot, "index.ts")
9
+
10
+ const env = {
11
+ ...process.env,
12
+ PI_SKIP_VERSION_CHECK: "1",
13
+ }
14
+ delete env.COMMANDCODE_API_KEY
15
+
16
+ const child = spawn(
17
+ "pi",
18
+ [
19
+ "--no-extensions",
20
+ "--extension",
21
+ extensionPath,
22
+ "--provider",
23
+ "commandcode",
24
+ "--model",
25
+ "gpt-5.6-luna",
26
+ "--models",
27
+ "commandcode/*",
28
+ ...process.argv.slice(2),
29
+ ],
30
+ {
31
+ cwd: repoRoot,
32
+ env,
33
+ stdio: "inherit",
34
+ },
35
+ )
36
+
37
+ for (const signal of ["SIGINT", "SIGTERM"]) {
38
+ process.on(signal, () => child.kill(signal))
39
+ }
40
+
41
+ child.once("error", (error) => {
42
+ console.error(`Could not start pi: ${error.message}`)
43
+ process.exitCode = 1
44
+ })
45
+
46
+ child.once("exit", (status, signal) => {
47
+ if (signal) process.kill(process.pid, signal)
48
+ process.exitCode = status ?? 1
49
+ })