pi-commandcode-provider 0.4.2 → 0.5.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/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.5.0 - 2026-08-07
6
+
7
+ - Stop replaying completed assistant reasoning traces to Command Code while preserving visible text and completed tool calls in follow-up request history.
8
+ - Add `/commandcode-refresh` and `/commandcode-status` commands for safe model-catalog refreshes and redacted diagnostics.
9
+ - 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.
10
+ - Normalize Command Code context overflow failures so pi can auto-compact and retry, while leaving unrelated rate-limit and capacity errors unchanged.
11
+ - 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.
12
+ - 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.
13
+ - 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.
14
+ - 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.
15
+ - 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.
16
+ - Fix cached input tokens being counted twice.
17
+
18
+ ### Contributors
19
+
20
+ - @IfkumRfnl — fixed cached input token accounting.
21
+
22
+ ## 0.4.3 - 2026-08-02
23
+
24
+ - 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.
25
+
26
+ ### Contributors
27
+
28
+ - @k3-2o — reported that the model-list fetch blocked pi startup when offline.
29
+
5
30
  ## 0.4.2 - 2026-07-05
6
31
 
7
32
  - Fix Oh My Pi extension validation by avoiding the missing `calculateCost` export from OMP's legacy `pi-ai` shim.
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:
32
+ Restart OMP or run `/reload`, then use `/login` and select **Use a subscription** followed by **Command Code**.
46
33
 
47
- ```txt
48
- /reload
49
- ```
34
+ ## Authentication
50
35
 
51
- ## Setup
36
+ ### Browser login
52
37
 
53
- Set your Command Code API key using one of these methods:
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.
54
39
 
55
- ### 1. Browser login (recommended)
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" />
56
41
 
57
- In pi, run:
42
+ If automatic transfer from the browser fails, copy the API key shown by Command Code and paste it into the terminal prompt.
58
43
 
59
- ```txt
60
- /login
61
- ```
62
-
63
- Then select **Command Code** from the provider list.
64
-
65
- <img width="1520" height="554" alt="image" src="https://github.com/user-attachments/assets/071e929a-6f49-4803-bfec-7a31368fb12a" />
66
-
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.
68
-
69
- > Note: `/login commandcode` is not supported by pi currently; use interactive `/login` and select Command Code.
70
-
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:
53
+
54
+ - `~/.commandcode/auth.json`
55
+ - `~/.pi/agent/auth.json`
56
+ - `~/.omp/agent/auth.json`
78
57
 
79
- Create `~/.commandcode/auth.json`:
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,53 +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 -->
128
-
129
- ## Model discovery
109
+ ## Model discovery and offline behavior
130
110
 
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
- For tests or local mocks, override it with `COMMANDCODE_MODELS_URL`.
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
+ This provider currently advertises and accepts **text input only**. The extension uses Command Code's legacy `/alpha/generate` protocol, while the public Provider API documentation describes image parts for its documented `/provider/v1` endpoints. The legacy request path has no documented image-part contract, and the model catalog fixture exposes model IDs and context lengths but no image capability or limit fields.
136
+
137
+ To avoid silently dropping or changing image data, the provider rejects image content in user messages and tool results before making a network request. It does not claim image capability or define image-size/count limits. This limitation can be revisited when Command Code documents image parts and limits for the protocol used here.
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
- ## Pricing
155
+ ```sh
156
+ pi remove npm:pi-commandcode-provider
157
+ ```
140
158
 
141
- 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.
159
+ For OMP:
142
160
 
143
- - 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 2× discount).
144
- - Models **not** in the table fall back to zero cost. When the Provider API adds a `cost` field, the static table can be removed.
161
+ ```sh
162
+ omp plugin upgrade pi-commandcode-provider
163
+ omp plugin uninstall pi-commandcode-provider
164
+ ```
145
165
 
146
- To add or update a price, edit the `MODEL_COSTS` record in `index.ts` and update the corresponding test in `tests/test-pricing.ts`.
166
+ ## Development
147
167
 
148
- ## Contributing
168
+ Start an isolated pi instance with only the current checkout installed and no existing Command Code credentials:
149
169
 
150
- See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, PR expectations, and commit message rules.
170
+ ```sh
171
+ npm run pi:isolated
172
+ ```
173
+
174
+ Run `/login` inside pi. Temporary credentials, configuration, and sessions are deleted when pi exits.
175
+
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
+ ```
151
181
 
152
- ## Release
182
+ Both commands accept additional pi arguments after `--`, for example `npm run pi:authenticated -- --model claude-sonnet-4-6`.
153
183
 
154
- 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.
155
185
 
156
186
  ## License
157
187
 
package/index.ts CHANGED
@@ -2,88 +2,49 @@
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 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"
16
+ import { join } from "node:path"
17
17
 
18
- import { COMMAND_CODE_CLI_VERSION, createStreamCommandCode, DEFAULT_API_BASE } from "./src/core.ts"
18
+ import {
19
+ COMMAND_CODE_CLI_VERSION,
20
+ COMMAND_CODE_INPUT_TYPES,
21
+ createStreamCommandCode,
22
+ DEFAULT_API_BASE,
23
+ } from "./src/core.ts"
19
24
  import { calculateCommandCodeCost } from "./src/cost.ts"
20
- import { DEFAULT_MODELS_URL, fetchCommandCodeModels } from "./src/models.ts"
21
- import { getApiKey, login, refreshToken } from "./src/oauth.ts"
22
-
23
- const API_BASE = process.env.COMMANDCODE_API_BASE ?? DEFAULT_API_BASE
24
- const MODELS_URL = process.env.COMMANDCODE_MODELS_URL ?? DEFAULT_MODELS_URL
25
-
26
- type CommandCodeModelCost = {
27
- input: number
28
- output: number
29
- cacheRead: number
30
- cacheWrite: number
31
- }
32
-
33
- const ZERO_MODEL_COST: CommandCodeModelCost = {
34
- input: 0,
35
- output: 0,
36
- cacheRead: 0,
37
- cacheWrite: 0,
38
- }
39
-
40
- // The Provider API supplies the current model list. Keep known display pricing
41
- // here until the Provider API exposes prices directly.
42
- const MODEL_COSTS: Record<string, CommandCodeModelCost> = {
43
- "claude-opus-4-7": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
44
- "claude-opus-4-6": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
45
- "claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
46
- "claude-haiku-4-5-20251001": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
47
- "gpt-5.5": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
48
- "gpt-5.4": { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 },
49
- "gpt-5.3-codex": { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 0 },
50
- "gpt-5.4-mini": { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
51
- "google/gemini-3.5-flash": { input: 1.5, output: 9, cacheRead: 0.15, cacheWrite: 0 },
52
- "google/gemini-3.1-flash-lite": { input: 0.25, output: 1.5, cacheRead: 0.03, cacheWrite: 0 },
53
- // 4× usage deal: 75% off (permanent, no expiry)
54
- "deepseek/deepseek-v4-pro": { input: 0.435, output: 0.87, cacheRead: 0.003625, cacheWrite: 0 },
55
- "deepseek/deepseek-v4-flash": { input: 0.14, output: 0.28, cacheRead: 0.028, cacheWrite: 0 },
56
- "moonshotai/Kimi-K2.6": { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0 },
57
- "moonshotai/Kimi-K2.5": { input: 0.6, output: 3, cacheRead: 0.1, cacheWrite: 0 },
58
- "zai-org/GLM-5.1": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
59
- "zai-org/GLM-5": { input: 1, output: 3.2, cacheRead: 0.2, cacheWrite: 0 },
60
- "MiniMaxAI/MiniMax-M2.7": { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
61
- "MiniMaxAI/MiniMax-M2.5": { input: 0.27, output: 0.95, cacheRead: 0.03, cacheWrite: 0 },
62
- "Qwen/Qwen3.6-Max-Preview": { input: 1.3, output: 7.8, cacheRead: 0.26, cacheWrite: 1.63 },
63
- "Qwen/Qwen3.6-Plus": { input: 0.5, output: 3, cacheRead: 0.1, cacheWrite: 0 },
64
- // 2× usage deal: 50% off through June 22, 2026
65
- "Qwen/Qwen3.7-Max": { input: 1.25, output: 3.75, cacheRead: 0.25, cacheWrite: 1.56 },
66
- "stepfun/Step-3.5-Flash": { input: 0.1, output: 0.3, cacheRead: 0.02, cacheWrite: 0 },
67
- "xiaomi/mimo-v2.5-pro": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
68
- "xiaomi/mimo-v2.5": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
69
- }
25
+ import {
26
+ DEFAULT_MODELS_URL,
27
+ getModelsTimeoutMs,
28
+ loadCommandCodeModels,
29
+ thinkingMetadataForModel,
30
+ type CommandCodeModel,
31
+ } from "./src/models.ts"
32
+ import { getApiKey as getOAuthApiKey, login, refreshToken } from "./src/oauth.ts"
33
+ import { MODEL_COSTS, ZERO_MODEL_COST } from "./src/pricing.ts"
34
+ import { createCommandCodeRuntime } from "./src/runtime.ts"
35
+ import { normalizeCommandCodeMessage } from "./src/overflow.ts"
70
36
 
71
- const streamCommandCode = createStreamCommandCode({
72
- createStream: () => new AssistantMessageEventStream(),
73
- calculateCost: calculateCommandCodeCost,
74
- apiBase: API_BASE,
75
- })
76
-
77
- // ---------------------------------------------------------------------------
78
- // Extension entry point
79
- // ---------------------------------------------------------------------------
80
-
81
- export default async function (pi: ExtensionAPI) {
82
- const models = await fetchCommandCodeModels({ url: MODELS_URL })
83
-
84
- pi.registerProvider("commandcode", {
37
+ function createProviderConfig(
38
+ models: readonly CommandCodeModel[],
39
+ apiBase: string,
40
+ streamCommandCode: ProviderConfig["streamSimple"],
41
+ ): ProviderConfig {
42
+ return {
85
43
  name: "Command Code",
86
- baseUrl: API_BASE,
44
+ baseUrl: apiBase,
45
+ // Keep environment authentication dynamic. OAuth credentials are resolved
46
+ // by pi's oauth registration, while the custom stream retains its own
47
+ // request-time legacy-file fallback for older compatible hosts.
87
48
  apiKey: "$COMMANDCODE_API_KEY",
88
49
  authHeader: true,
89
50
  api: "commandcode-custom",
@@ -96,16 +57,60 @@ export default async function (pi: ExtensionAPI) {
96
57
  name: "Command Code",
97
58
  login,
98
59
  refreshToken,
99
- getApiKey,
60
+ getApiKey: getOAuthApiKey,
100
61
  },
101
- models: models.map((model) => ({
102
- id: model.id,
103
- name: model.name,
104
- reasoning: model.reasoning,
105
- input: ["text"] as const,
106
- cost: MODEL_COSTS[model.id] ?? ZERO_MODEL_COST,
107
- contextWindow: model.contextWindow,
108
- maxTokens: model.maxTokens,
109
- })),
62
+ models: models.map(createProviderModel),
63
+ }
64
+ }
65
+
66
+ function createProviderModel(model: {
67
+ id: string
68
+ name: string
69
+ reasoning: boolean
70
+ contextWindow: number
71
+ maxTokens: number
72
+ }) {
73
+ return {
74
+ id: model.id,
75
+ name: model.name,
76
+ reasoning: model.reasoning,
77
+ ...(thinkingMetadataForModel(model.id) ?? {}),
78
+ input: COMMAND_CODE_INPUT_TYPES,
79
+ cost: MODEL_COSTS[model.id] ?? ZERO_MODEL_COST,
80
+ contextWindow: model.contextWindow,
81
+ maxTokens: model.maxTokens,
82
+ } as const
83
+ }
84
+
85
+ export default async function (pi: ExtensionAPI) {
86
+ const apiBase = process.env.COMMANDCODE_API_BASE ?? DEFAULT_API_BASE
87
+ const modelsUrl = process.env.COMMANDCODE_MODELS_URL ?? DEFAULT_MODELS_URL
88
+ const modelsTimeoutMs = getModelsTimeoutMs()
89
+ const modelsCachePath =
90
+ process.env.COMMANDCODE_MODELS_CACHE ?? join(getAgentDir(), "commandcode-models.json")
91
+ const streamCommandCode = createStreamCommandCode({
92
+ createStream: () => new AssistantMessageEventStream(),
93
+ calculateCost: calculateCommandCodeCost,
94
+ apiBase,
110
95
  })
96
+
97
+ pi.on("message_end", async (event, ctx) => {
98
+ if (event.message.role !== "assistant") return
99
+ const normalized = normalizeCommandCodeMessage(event.message, ctx.model?.provider)
100
+ return normalized ? { message: normalized.message } : undefined
101
+ })
102
+
103
+ const runtime = createCommandCodeRuntime<ProviderConfig, ExtensionCommandContext>(pi, {
104
+ endpoint: modelsUrl,
105
+ cachePath: modelsCachePath,
106
+ loadModels: () =>
107
+ loadCommandCodeModels({
108
+ url: modelsUrl,
109
+ cachePath: modelsCachePath,
110
+ timeoutMs: modelsTimeoutMs,
111
+ }),
112
+ createProviderConfig: (models) => createProviderConfig(models, apiBase, streamCommandCode),
113
+ })
114
+
115
+ await runtime.initialize()
111
116
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-commandcode-provider",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
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
+ })