pi-commandcode-provider 0.1.1-next.0 → 0.3.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 +28 -0
- package/CONTRIBUTING.md +136 -0
- package/README.md +40 -9
- package/RELEASE.md +192 -0
- package/index.ts +49 -4
- package/package.json +15 -4
- package/src/converters.ts +32 -1
- package/src/core.ts +47 -30
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.0 - 2026-05-28
|
|
4
|
+
|
|
5
|
+
- Add OMP (Oh My Pi) provider compatibility: support `~/.omp/agent/auth.json` auth path, handle OMP's env-var-name-as-apiKey quirk, convert OMP system prompt arrays to text.
|
|
6
|
+
- Close open thinking blocks before starting text or tool output to prevent event ordering issues when upstream omits `reasoning-end`.
|
|
7
|
+
- Correct DeepSeek V4 Pro discount as permanent (no expiry), not time-limited.
|
|
8
|
+
- Correct DeepSeek V4 Flash cache-read rate to $0.028/M and add xiaomi/mimo models to pricing table.
|
|
9
|
+
- Upgrade pi dependencies from `@mariozechner` 0.72.0 to `@earendil-works` 0.75.5.
|
|
10
|
+
- Move `pi-coding-agent` to optional peerDependencies.
|
|
11
|
+
|
|
12
|
+
## 0.2.0 - 2026-05-27
|
|
13
|
+
|
|
14
|
+
- Stream `reasoning-delta` events incrementally instead of buffering the full thinking block until `reasoning-end`. Emits `thinking_start`, `thinking_delta`, and `thinking_end` events as they arrive so the UI can show reasoning in real time.
|
|
15
|
+
- Close open text blocks on `reasoning-start` and `reasoning-delta` so thinking and text never overlap in the output.
|
|
16
|
+
- Add live display pricing (`MODEL_COSTS`) for known Command Code models. Cost falls back to zero for models not yet in the price table until the Provider API exposes pricing directly.
|
|
17
|
+
- Fetch models from the Command Code Provider API at startup (inherited from upstream 0.1.1) and overlay the static cost table.
|
|
18
|
+
|
|
19
|
+
## 0.1.1 - 2026-05-26
|
|
20
|
+
|
|
21
|
+
- Align Command Code generate requests with CLI `0.27.2` headers and payload shape.
|
|
22
|
+
- Support official Command Code CLI auth files using the `command-code` credential key.
|
|
23
|
+
- Handle `reasoning-start` and ignore streamed `tool-result` events.
|
|
24
|
+
- Cap generated `max_tokens` by the selected model and the Command Code output limit.
|
|
25
|
+
|
|
26
|
+
## 0.1.0 - 2026-05-05
|
|
27
|
+
|
|
28
|
+
- Initial public release.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping improve `pi-commandcode-provider`.
|
|
4
|
+
|
|
5
|
+
This is an unofficial Command Code provider for pi. Keep changes small, tested, and easy to review.
|
|
6
|
+
|
|
7
|
+
## Development setup
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install
|
|
11
|
+
npm test
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Useful commands:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npm run typecheck
|
|
18
|
+
npm run format:check
|
|
19
|
+
npm run test:unit
|
|
20
|
+
npm run test:models
|
|
21
|
+
npm run test:oauth
|
|
22
|
+
npm run test:abort
|
|
23
|
+
npm run test:stream
|
|
24
|
+
npm run test:pi-local
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Before opening a PR, run:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm test
|
|
31
|
+
npm run format:check
|
|
32
|
+
git diff --check
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For release and npm smoke-test steps, see [RELEASE.md](RELEASE.md).
|
|
36
|
+
|
|
37
|
+
## Pull request guidelines
|
|
38
|
+
|
|
39
|
+
- Keep PRs focused on one problem or feature.
|
|
40
|
+
- Add or update tests for behavior changes.
|
|
41
|
+
- Update `README.md`, `CHANGELOG.md`, or `RELEASE.md` when user-facing behavior changes.
|
|
42
|
+
- Avoid broad refactors unless the PR is specifically about refactoring.
|
|
43
|
+
- Do not include API keys, tokens, real auth files, `.env` files, or other secrets.
|
|
44
|
+
- Prefer documented/public Command Code API behavior. If compatibility with CLI behavior is needed, document why.
|
|
45
|
+
- Make sure npm package contents still make sense when `package.json` `files` changes.
|
|
46
|
+
|
|
47
|
+
## Testing pi integration changes
|
|
48
|
+
|
|
49
|
+
For provider, auth, request-shape, or stream changes, test both local code and the package form when possible.
|
|
50
|
+
|
|
51
|
+
Local extension smoke:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
pi --no-extensions -e ./index.ts --list-models commandcode
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Npm package smoke and isolated `/login` testing are documented in [RELEASE.md](RELEASE.md#test-the-npm-package-in-pi).
|
|
58
|
+
|
|
59
|
+
## Commit message rules
|
|
60
|
+
|
|
61
|
+
Use Angular-style Conventional Commits.
|
|
62
|
+
|
|
63
|
+
Format:
|
|
64
|
+
|
|
65
|
+
```txt
|
|
66
|
+
<type>(<scope>): <subject>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Examples:
|
|
70
|
+
|
|
71
|
+
```txt
|
|
72
|
+
feat(auth): support Command Code CLI auth files
|
|
73
|
+
fix(core): cap max tokens by selected model
|
|
74
|
+
docs(release): document npm smoke testing
|
|
75
|
+
test(stream): cover reasoning start events
|
|
76
|
+
chore(release): publish 0.1.1
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Types
|
|
80
|
+
|
|
81
|
+
Use one of these types:
|
|
82
|
+
|
|
83
|
+
- `feat`: a new user-facing feature
|
|
84
|
+
- `fix`: a bug fix
|
|
85
|
+
- `docs`: documentation-only changes
|
|
86
|
+
- `style`: formatting-only changes, no behavior change
|
|
87
|
+
- `refactor`: code restructuring without behavior change
|
|
88
|
+
- `perf`: performance improvement
|
|
89
|
+
- `test`: adding or changing tests
|
|
90
|
+
- `build`: package, dependency, or build-system changes
|
|
91
|
+
- `ci`: CI workflow changes
|
|
92
|
+
- `chore`: maintenance that does not fit another type
|
|
93
|
+
- `revert`: revert a previous commit
|
|
94
|
+
|
|
95
|
+
### Scopes
|
|
96
|
+
|
|
97
|
+
Use a short lowercase scope. Prefer existing project areas:
|
|
98
|
+
|
|
99
|
+
- `auth`
|
|
100
|
+
- `oauth`
|
|
101
|
+
- `core`
|
|
102
|
+
- `models`
|
|
103
|
+
- `stream`
|
|
104
|
+
- `tests`
|
|
105
|
+
- `docs`
|
|
106
|
+
- `release`
|
|
107
|
+
- `deps`
|
|
108
|
+
- `ci`
|
|
109
|
+
|
|
110
|
+
A scope is strongly recommended. If no scope fits, choose the closest project area instead of omitting it.
|
|
111
|
+
|
|
112
|
+
### Subject line
|
|
113
|
+
|
|
114
|
+
- Use imperative mood: `fix(auth): read oauth credentials`, not `fixed` or `fixes`.
|
|
115
|
+
- Keep it concise.
|
|
116
|
+
- Start lowercase after the colon.
|
|
117
|
+
- Do not end with a period.
|
|
118
|
+
|
|
119
|
+
### Body and footers
|
|
120
|
+
|
|
121
|
+
Use a body when the reason is not obvious:
|
|
122
|
+
|
|
123
|
+
```txt
|
|
124
|
+
fix(core): cap max tokens by selected model
|
|
125
|
+
|
|
126
|
+
Command Code can return models with lower output limits than the provider-wide cap.
|
|
127
|
+
Clamp defaults to the selected model so requests do not exceed upstream limits.
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Breaking changes must be marked with `!` or a `BREAKING CHANGE:` footer:
|
|
131
|
+
|
|
132
|
+
```txt
|
|
133
|
+
feat(api)!: switch to provider api endpoints
|
|
134
|
+
|
|
135
|
+
BREAKING CHANGE: removes support for the legacy internal generate endpoint.
|
|
136
|
+
```
|
package/README.md
CHANGED
|
@@ -36,6 +36,18 @@ Then reload pi:
|
|
|
36
36
|
/reload
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
### Oh My Pi
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
omp plugin install pi-commandcode-provider
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then restart OMP or run:
|
|
46
|
+
|
|
47
|
+
```txt
|
|
48
|
+
/reload
|
|
49
|
+
```
|
|
50
|
+
|
|
39
51
|
## Setup
|
|
40
52
|
|
|
41
53
|
Set your Command Code API key using one of these methods:
|
|
@@ -83,7 +95,7 @@ The official Command Code CLI auth shape is also supported:
|
|
|
83
95
|
}
|
|
84
96
|
```
|
|
85
97
|
|
|
86
|
-
Or use pi
|
|
98
|
+
Or use a pi/OMP auth file at `~/.pi/agent/auth.json` or `~/.omp/agent/auth.json`:
|
|
87
99
|
|
|
88
100
|
```json
|
|
89
101
|
{
|
|
@@ -99,12 +111,21 @@ After installing and setting your API key, select a Command Code model in pi:
|
|
|
99
111
|
/model deepseek/deepseek-v4-flash
|
|
100
112
|
```
|
|
101
113
|
|
|
102
|
-
Any query will then use the Command Code API. You can list available models
|
|
114
|
+
Any query will then use the Command Code API. You can list available models:
|
|
103
115
|
|
|
104
|
-
```
|
|
105
|
-
/models
|
|
116
|
+
```sh
|
|
117
|
+
pi -e index.ts --list-models # or /models within pi
|
|
118
|
+
omp -e index.ts --list-models
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
In OMP, use the provider-qualified model name:
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
omp -p "hello" --model commandcode/deepseek/deepseek-v4-flash
|
|
106
125
|
```
|
|
107
126
|
|
|
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
|
+
|
|
108
129
|
## Model discovery
|
|
109
130
|
|
|
110
131
|
On startup, the provider fetches:
|
|
@@ -115,12 +136,22 @@ https://api.commandcode.ai/provider/v1/models
|
|
|
115
136
|
|
|
116
137
|
For tests or local mocks, override it with `COMMANDCODE_MODELS_URL`.
|
|
117
138
|
|
|
118
|
-
##
|
|
139
|
+
## Pricing
|
|
119
140
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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.
|
|
142
|
+
|
|
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.
|
|
145
|
+
|
|
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`.
|
|
147
|
+
|
|
148
|
+
## Contributing
|
|
149
|
+
|
|
150
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, PR expectations, and commit message rules.
|
|
151
|
+
|
|
152
|
+
## Release
|
|
153
|
+
|
|
154
|
+
See [RELEASE.md](RELEASE.md) for the prerelease, npm smoke-test, stable publish, git tag, and GitHub follow-up checklist.
|
|
124
155
|
|
|
125
156
|
## License
|
|
126
157
|
|
package/RELEASE.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Release Process
|
|
2
|
+
|
|
3
|
+
This project uses npm semver releases.
|
|
4
|
+
|
|
5
|
+
Recommended flow:
|
|
6
|
+
|
|
7
|
+
- publish prereleases with the `next` dist-tag
|
|
8
|
+
- smoke-test the npm package directly in pi
|
|
9
|
+
- publish stable releases with the `latest` dist-tag
|
|
10
|
+
- commit and tag the stable release
|
|
11
|
+
- comment on the related PR or issue after shipping
|
|
12
|
+
|
|
13
|
+
## Prerelease flow
|
|
14
|
+
|
|
15
|
+
Use `next` for beta/alpha/manual validation builds.
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm version prepatch --preid next --no-git-tag-version
|
|
19
|
+
npm test
|
|
20
|
+
npm run format:check
|
|
21
|
+
npm pack --dry-run
|
|
22
|
+
npm publish --tag next --access public
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
If npm asks for browser or OTP auth, run the publish command manually and complete the npm prompt.
|
|
26
|
+
|
|
27
|
+
Verify the registry state:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm view pi-commandcode-provider@next version dist-tags --json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Expected:
|
|
34
|
+
|
|
35
|
+
- `next` points to the prerelease version
|
|
36
|
+
- `latest` still points to the previous stable version
|
|
37
|
+
|
|
38
|
+
## Test the npm package in pi
|
|
39
|
+
|
|
40
|
+
Always test from npm, not the local checkout.
|
|
41
|
+
|
|
42
|
+
### 1. Model discovery smoke test
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
PI_SKIP_VERSION_CHECK=1 \
|
|
46
|
+
pi --no-extensions \
|
|
47
|
+
-e npm:pi-commandcode-provider@next \
|
|
48
|
+
--list-models commandcode
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Expected:
|
|
52
|
+
|
|
53
|
+
- provider `commandcode` appears
|
|
54
|
+
- live Command Code models are listed
|
|
55
|
+
|
|
56
|
+
### 2. Manual `/login` test with isolated pi config
|
|
57
|
+
|
|
58
|
+
Use temporary pi config and session directories so the test does not touch your real pi auth.
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
export PI_CC_TEST_AGENT_DIR="$(mktemp -d)"
|
|
62
|
+
export PI_CC_TEST_SESSION_DIR="$(mktemp -d)"
|
|
63
|
+
|
|
64
|
+
export PI_CODING_AGENT_DIR="$PI_CC_TEST_AGENT_DIR"
|
|
65
|
+
export PI_CODING_AGENT_SESSION_DIR="$PI_CC_TEST_SESSION_DIR"
|
|
66
|
+
export PI_SKIP_VERSION_CHECK=1
|
|
67
|
+
|
|
68
|
+
pi --no-extensions \
|
|
69
|
+
-e npm:pi-commandcode-provider@next \
|
|
70
|
+
--provider commandcode \
|
|
71
|
+
--model deepseek/deepseek-v4-flash
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Inside pi:
|
|
75
|
+
|
|
76
|
+
```txt
|
|
77
|
+
/login
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Then:
|
|
81
|
+
|
|
82
|
+
1. choose **Use a subscription**
|
|
83
|
+
2. choose **Command Code**
|
|
84
|
+
3. complete the browser auth flow
|
|
85
|
+
4. if automatic transfer fails, paste the copied Command Code API key into pi
|
|
86
|
+
5. send this message:
|
|
87
|
+
|
|
88
|
+
```txt
|
|
89
|
+
Reply exactly: manual-npm-ok
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Expected:
|
|
93
|
+
|
|
94
|
+
- login succeeds
|
|
95
|
+
- a Command Code credential is saved under the temporary `PI_CODING_AGENT_DIR`
|
|
96
|
+
- the model replies exactly `manual-npm-ok`
|
|
97
|
+
|
|
98
|
+
### 3. Post-login print-mode test
|
|
99
|
+
|
|
100
|
+
Using the same exported temp variables from above:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
pi --no-extensions \
|
|
104
|
+
-e npm:pi-commandcode-provider@next \
|
|
105
|
+
--no-session \
|
|
106
|
+
-p \
|
|
107
|
+
--provider commandcode \
|
|
108
|
+
--model deepseek/deepseek-v4-flash \
|
|
109
|
+
"Reply exactly: manual-npm-ok"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Expected:
|
|
113
|
+
|
|
114
|
+
```txt
|
|
115
|
+
manual-npm-ok
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 4. Cleanup isolated pi config
|
|
119
|
+
|
|
120
|
+
Only run this if these variables were created by the test above:
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
rm -rf "$PI_CC_TEST_AGENT_DIR" "$PI_CC_TEST_SESSION_DIR"
|
|
124
|
+
unset PI_CC_TEST_AGENT_DIR PI_CC_TEST_SESSION_DIR
|
|
125
|
+
unset PI_CODING_AGENT_DIR PI_CODING_AGENT_SESSION_DIR PI_SKIP_VERSION_CHECK
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Stable release flow
|
|
129
|
+
|
|
130
|
+
After the `next` package is verified, set the intended stable version:
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
npm version 0.1.1 --no-git-tag-version
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Replace `0.1.1` with the intended stable version.
|
|
137
|
+
|
|
138
|
+
Update `CHANGELOG.md`, then run checks:
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
npm test
|
|
142
|
+
npm run format:check
|
|
143
|
+
npm pack --dry-run
|
|
144
|
+
git diff --check
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Commit and tag:
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
git add .
|
|
151
|
+
git commit -m "Release 0.1.1"
|
|
152
|
+
git tag -a v0.1.1 -m "Release 0.1.1"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Publish stable:
|
|
156
|
+
|
|
157
|
+
```sh
|
|
158
|
+
npm publish --tag latest --access public
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
If npm asks for browser or OTP auth, run the publish command manually and complete the npm prompt.
|
|
162
|
+
|
|
163
|
+
Verify npm:
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
npm view pi-commandcode-provider version dist-tags --json
|
|
167
|
+
npm view pi-commandcode-provider@0.1.1 version --json
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Expected:
|
|
171
|
+
|
|
172
|
+
- `latest` points to the stable version
|
|
173
|
+
- the stable version exists on npm
|
|
174
|
+
|
|
175
|
+
Push commit and tag:
|
|
176
|
+
|
|
177
|
+
```sh
|
|
178
|
+
git push origin main
|
|
179
|
+
git push origin v0.1.1
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## GitHub follow-up
|
|
183
|
+
|
|
184
|
+
Comment on the related PR and issue after publishing and pushing:
|
|
185
|
+
|
|
186
|
+
```sh
|
|
187
|
+
gh pr comment <number> --body "Shipped in \`pi-commandcode-provider@0.1.1\` / tag \`v0.1.1\`."
|
|
188
|
+
|
|
189
|
+
gh issue comment <number> --body "Shipped in \`pi-commandcode-provider@0.1.1\` / tag \`v0.1.1\`."
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Only comment on PRs or issues actually included in the release.
|
package/index.ts
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* Models are fetched from Command Code's Provider API at startup.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import {
|
|
16
|
-
import type { ExtensionAPI } from "@
|
|
15
|
+
import { AssistantMessageEventStream, calculateCost } from "@earendil-works/pi-ai"
|
|
16
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"
|
|
17
17
|
|
|
18
18
|
import { COMMAND_CODE_CLI_VERSION, createStreamCommandCode, DEFAULT_API_BASE } from "./src/core.ts"
|
|
19
19
|
import { DEFAULT_MODELS_URL, fetchCommandCodeModels } from "./src/models.ts"
|
|
@@ -22,8 +22,53 @@ import { getApiKey, login, refreshToken } from "./src/oauth.ts"
|
|
|
22
22
|
const API_BASE = process.env.COMMANDCODE_API_BASE ?? DEFAULT_API_BASE
|
|
23
23
|
const MODELS_URL = process.env.COMMANDCODE_MODELS_URL ?? DEFAULT_MODELS_URL
|
|
24
24
|
|
|
25
|
+
type CommandCodeModelCost = {
|
|
26
|
+
input: number
|
|
27
|
+
output: number
|
|
28
|
+
cacheRead: number
|
|
29
|
+
cacheWrite: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const ZERO_MODEL_COST: CommandCodeModelCost = {
|
|
33
|
+
input: 0,
|
|
34
|
+
output: 0,
|
|
35
|
+
cacheRead: 0,
|
|
36
|
+
cacheWrite: 0,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The Provider API supplies the current model list. Keep known display pricing
|
|
40
|
+
// here until the Provider API exposes prices directly.
|
|
41
|
+
const MODEL_COSTS: Record<string, CommandCodeModelCost> = {
|
|
42
|
+
"claude-opus-4-7": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
43
|
+
"claude-opus-4-6": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
44
|
+
"claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
45
|
+
"claude-haiku-4-5-20251001": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
46
|
+
"gpt-5.5": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
|
47
|
+
"gpt-5.4": { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 },
|
|
48
|
+
"gpt-5.3-codex": { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 0 },
|
|
49
|
+
"gpt-5.4-mini": { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
|
|
50
|
+
"google/gemini-3.5-flash": { input: 1.5, output: 9, cacheRead: 0.15, cacheWrite: 0 },
|
|
51
|
+
"google/gemini-3.1-flash-lite": { input: 0.25, output: 1.5, cacheRead: 0.03, cacheWrite: 0 },
|
|
52
|
+
// 4× usage deal: 75% off (permanent, no expiry)
|
|
53
|
+
"deepseek/deepseek-v4-pro": { input: 0.435, output: 0.87, cacheRead: 0.003625, cacheWrite: 0 },
|
|
54
|
+
"deepseek/deepseek-v4-flash": { input: 0.14, output: 0.28, cacheRead: 0.028, cacheWrite: 0 },
|
|
55
|
+
"moonshotai/Kimi-K2.6": { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0 },
|
|
56
|
+
"moonshotai/Kimi-K2.5": { input: 0.6, output: 3, cacheRead: 0.1, cacheWrite: 0 },
|
|
57
|
+
"zai-org/GLM-5.1": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
|
58
|
+
"zai-org/GLM-5": { input: 1, output: 3.2, cacheRead: 0.2, cacheWrite: 0 },
|
|
59
|
+
"MiniMaxAI/MiniMax-M2.7": { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
|
|
60
|
+
"MiniMaxAI/MiniMax-M2.5": { input: 0.27, output: 0.95, cacheRead: 0.03, cacheWrite: 0 },
|
|
61
|
+
"Qwen/Qwen3.6-Max-Preview": { input: 1.3, output: 7.8, cacheRead: 0.26, cacheWrite: 1.63 },
|
|
62
|
+
"Qwen/Qwen3.6-Plus": { input: 0.5, output: 3, cacheRead: 0.1, cacheWrite: 0 },
|
|
63
|
+
// 2× usage deal: 50% off through June 22, 2026
|
|
64
|
+
"Qwen/Qwen3.7-Max": { input: 1.25, output: 3.75, cacheRead: 0.25, cacheWrite: 1.56 },
|
|
65
|
+
"stepfun/Step-3.5-Flash": { input: 0.1, output: 0.3, cacheRead: 0.02, cacheWrite: 0 },
|
|
66
|
+
"xiaomi/mimo-v2.5-pro": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
67
|
+
"xiaomi/mimo-v2.5": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
68
|
+
}
|
|
69
|
+
|
|
25
70
|
const streamCommandCode = createStreamCommandCode({
|
|
26
|
-
createStream:
|
|
71
|
+
createStream: () => new AssistantMessageEventStream(),
|
|
27
72
|
calculateCost,
|
|
28
73
|
apiBase: API_BASE,
|
|
29
74
|
})
|
|
@@ -57,7 +102,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
57
102
|
name: model.name,
|
|
58
103
|
reasoning: model.reasoning,
|
|
59
104
|
input: ["text"] as const,
|
|
60
|
-
cost:
|
|
105
|
+
cost: MODEL_COSTS[model.id] ?? ZERO_MODEL_COST,
|
|
61
106
|
contextWindow: model.contextWindow,
|
|
62
107
|
maxTokens: model.maxTokens,
|
|
63
108
|
})),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-commandcode-provider",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "pi custom provider for Command Code API (commandcode.ai)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -22,15 +22,19 @@
|
|
|
22
22
|
"index.ts",
|
|
23
23
|
"src/",
|
|
24
24
|
"README.md",
|
|
25
|
+
"CHANGELOG.md",
|
|
26
|
+
"CONTRIBUTING.md",
|
|
27
|
+
"RELEASE.md",
|
|
25
28
|
"LICENSE"
|
|
26
29
|
],
|
|
27
30
|
"scripts": {
|
|
28
|
-
"test": "npm run typecheck && tsx tests/test-pure-functions.ts && tsx tests/test-models.ts && tsx tests/test-oauth.ts && tsx tests/test-abort.ts && tsx tests/test-stream.ts && node tests/test-pi-local.mjs",
|
|
31
|
+
"test": "npm run typecheck && tsx tests/test-pure-functions.ts && tsx tests/test-models.ts && tsx tests/test-pricing.ts && tsx tests/test-oauth.ts && tsx tests/test-abort.ts && tsx tests/test-stream.ts && node tests/test-pi-local.mjs && node tests/test-omp-compat.mjs",
|
|
29
32
|
"typecheck": "tsc --noEmit",
|
|
30
33
|
"format:check": "prettier --check '**/*.{ts,mjs,json,md}'",
|
|
31
34
|
"format": "prettier --write '**/*.{ts,mjs,json,md}'",
|
|
32
35
|
"test:unit": "tsx tests/test-pure-functions.ts",
|
|
33
36
|
"test:models": "tsx tests/test-models.ts",
|
|
37
|
+
"test:pricing": "tsx tests/test-pricing.ts",
|
|
34
38
|
"test:oauth": "tsx tests/test-oauth.ts",
|
|
35
39
|
"test:abort": "tsx tests/test-abort.ts",
|
|
36
40
|
"test:stream": "tsx tests/test-stream.ts",
|
|
@@ -43,13 +47,20 @@
|
|
|
43
47
|
]
|
|
44
48
|
},
|
|
45
49
|
"devDependencies": {
|
|
46
|
-
"@mariozechner/pi-coding-agent": "0.72.0",
|
|
47
50
|
"@types/node": "25.6.0",
|
|
48
51
|
"prettier": "^3.5.0",
|
|
49
52
|
"tsx": "4.21.0",
|
|
50
53
|
"typescript": "6.0.3"
|
|
51
54
|
},
|
|
52
55
|
"dependencies": {
|
|
53
|
-
"@
|
|
56
|
+
"@earendil-works/pi-ai": "0.75.5"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@earendil-works/pi-coding-agent": "^0.75.5"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"@earendil-works/pi-coding-agent": {
|
|
63
|
+
"optional": true
|
|
64
|
+
}
|
|
54
65
|
}
|
|
55
66
|
}
|
package/src/converters.ts
CHANGED
|
@@ -39,7 +39,11 @@ export function numberValue(value: unknown): number | undefined {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function defaultAuthPaths(home: string): string[] {
|
|
42
|
-
return [
|
|
42
|
+
return [
|
|
43
|
+
join(home, ".commandcode", "auth.json"),
|
|
44
|
+
join(home, ".omp", "agent", "auth.json"),
|
|
45
|
+
join(home, ".pi", "agent", "auth.json"),
|
|
46
|
+
]
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
function apiKeyFromCredentialRecord(value: unknown): string | undefined {
|
|
@@ -284,3 +288,30 @@ export function mapFinishReason(reason: unknown): StopReason {
|
|
|
284
288
|
}
|
|
285
289
|
return "stop"
|
|
286
290
|
}
|
|
291
|
+
|
|
292
|
+
function promptPartToText(value: unknown, depth = 0): string {
|
|
293
|
+
if (depth > 10) return ""
|
|
294
|
+
if (typeof value === "string") return value
|
|
295
|
+
if (Array.isArray(value))
|
|
296
|
+
return value
|
|
297
|
+
.map((v) => promptPartToText(v, depth + 1))
|
|
298
|
+
.filter(Boolean)
|
|
299
|
+
.join("\n")
|
|
300
|
+
if (!isRecord(value)) return ""
|
|
301
|
+
const text = stringValue(value.text)
|
|
302
|
+
if (text) return text
|
|
303
|
+
const content = promptPartToText(value.content, depth + 1)
|
|
304
|
+
if (content) return content
|
|
305
|
+
return ""
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function systemPromptToText(value: unknown): string {
|
|
309
|
+
if (value === undefined || value === null) return ""
|
|
310
|
+
if (typeof value === "string") return value
|
|
311
|
+
if (Array.isArray(value))
|
|
312
|
+
return value
|
|
313
|
+
.map((v) => promptPartToText(v, 0))
|
|
314
|
+
.filter(Boolean)
|
|
315
|
+
.join("\n\n")
|
|
316
|
+
return promptPartToText(value, 0)
|
|
317
|
+
}
|
package/src/core.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
recordOrEmpty,
|
|
19
19
|
stringValue,
|
|
20
20
|
toolsToJson,
|
|
21
|
+
systemPromptToText,
|
|
21
22
|
} from "./converters.ts"
|
|
22
23
|
import type {
|
|
23
24
|
AssistantMessageEventStreamLike,
|
|
@@ -131,8 +132,13 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
131
132
|
const stream = deps.createStream()
|
|
132
133
|
|
|
133
134
|
async function run() {
|
|
135
|
+
// OMP may pass the env-var name "COMMANDCODE_API_KEY" as the apiKey
|
|
136
|
+
// value instead of resolving it. Filter out this specific string.
|
|
137
|
+
const hostKey =
|
|
138
|
+
options?.apiKey && options.apiKey !== "COMMANDCODE_API_KEY" ? options.apiKey : undefined
|
|
139
|
+
|
|
134
140
|
const apiKey =
|
|
135
|
-
|
|
141
|
+
hostKey ??
|
|
136
142
|
getApiKey({
|
|
137
143
|
env: deps.env,
|
|
138
144
|
authPaths: deps.authPaths,
|
|
@@ -149,7 +155,7 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
149
155
|
usage: defaultUsage(),
|
|
150
156
|
stopReason: "error",
|
|
151
157
|
errorMessage:
|
|
152
|
-
"No Command Code API key. Run /login and select Command Code, set COMMANDCODE_API_KEY env var, or configure ~/.commandcode/auth.json or ~/.
|
|
158
|
+
"No Command Code API key. Run /login and select Command Code, set the COMMANDCODE_API_KEY env var, or configure ~/.commandcode/auth.json, ~/.pi/agent/auth.json or ~/.omp/agent/auth.json",
|
|
153
159
|
timestamp: now(),
|
|
154
160
|
}
|
|
155
161
|
stream.push({ type: "error", reason: "error", error: msg })
|
|
@@ -172,7 +178,7 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
172
178
|
let reader: ReadableStreamDefaultReader<Uint8Array> | undefined
|
|
173
179
|
let textBlock: TextContent | undefined
|
|
174
180
|
let currentTextIdx = -1
|
|
175
|
-
let
|
|
181
|
+
let thinkingIdx = -1
|
|
176
182
|
let finished = false
|
|
177
183
|
|
|
178
184
|
const abortUpstream = () => {
|
|
@@ -204,29 +210,18 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
204
210
|
currentTextIdx = -1
|
|
205
211
|
}
|
|
206
212
|
|
|
207
|
-
const
|
|
208
|
-
if (
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
type: "thinking_delta",
|
|
220
|
-
contentIndex: idx,
|
|
221
|
-
delta: thinkingText,
|
|
222
|
-
partial: output,
|
|
223
|
-
})
|
|
224
|
-
stream.push({
|
|
225
|
-
type: "thinking_end",
|
|
226
|
-
contentIndex: idx,
|
|
227
|
-
content: thinkingText,
|
|
228
|
-
partial: output,
|
|
229
|
-
})
|
|
213
|
+
const endThinking = () => {
|
|
214
|
+
if (thinkingIdx < 0) return
|
|
215
|
+
const tc = output.content[thinkingIdx]
|
|
216
|
+
if (tc && tc.type === "thinking") {
|
|
217
|
+
stream.push({
|
|
218
|
+
type: "thinking_end",
|
|
219
|
+
contentIndex: thinkingIdx,
|
|
220
|
+
content: (tc as { thinking: string }).thinking,
|
|
221
|
+
partial: output,
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
thinkingIdx = -1
|
|
230
225
|
}
|
|
231
226
|
|
|
232
227
|
const handleEvent = (event: unknown) => {
|
|
@@ -234,6 +229,7 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
234
229
|
|
|
235
230
|
switch (event.type) {
|
|
236
231
|
case "text-delta": {
|
|
232
|
+
endThinking()
|
|
237
233
|
if (!textBlock) {
|
|
238
234
|
textBlock = { type: "text", text: "" }
|
|
239
235
|
output.content.push(textBlock)
|
|
@@ -262,12 +258,32 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
262
258
|
|
|
263
259
|
case "reasoning-delta": {
|
|
264
260
|
endTextBlock()
|
|
265
|
-
|
|
261
|
+
const delta = stringValue(event.text) ?? ""
|
|
262
|
+
if (thinkingIdx < 0) {
|
|
263
|
+
output.content.push({ type: "thinking", thinking: delta })
|
|
264
|
+
thinkingIdx = output.content.length - 1
|
|
265
|
+
stream.push({
|
|
266
|
+
type: "thinking_start",
|
|
267
|
+
contentIndex: thinkingIdx,
|
|
268
|
+
partial: output,
|
|
269
|
+
})
|
|
270
|
+
} else {
|
|
271
|
+
const tc = output.content[thinkingIdx]
|
|
272
|
+
if (tc && tc.type === "thinking") {
|
|
273
|
+
;(tc as { thinking: string }).thinking += delta
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
stream.push({
|
|
277
|
+
type: "thinking_delta",
|
|
278
|
+
contentIndex: thinkingIdx,
|
|
279
|
+
delta,
|
|
280
|
+
partial: output,
|
|
281
|
+
})
|
|
266
282
|
break
|
|
267
283
|
}
|
|
268
284
|
|
|
269
285
|
case "reasoning-end": {
|
|
270
|
-
|
|
286
|
+
endThinking()
|
|
271
287
|
break
|
|
272
288
|
}
|
|
273
289
|
|
|
@@ -277,6 +293,7 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
277
293
|
|
|
278
294
|
case "tool-call": {
|
|
279
295
|
endTextBlock()
|
|
296
|
+
endThinking()
|
|
280
297
|
const toolCall: ToolCallContent = {
|
|
281
298
|
type: "toolCall",
|
|
282
299
|
id: stringValue(event.toolCallId) ?? "",
|
|
@@ -355,7 +372,7 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
355
372
|
model: model.id,
|
|
356
373
|
messages: messagesToCC(context.messages),
|
|
357
374
|
tools: toolsToJson(context.tools),
|
|
358
|
-
system: context.systemPrompt
|
|
375
|
+
system: systemPromptToText(context.systemPrompt),
|
|
359
376
|
max_tokens: generateMaxTokens(model, options),
|
|
360
377
|
temperature: 0.3,
|
|
361
378
|
stream: true,
|
|
@@ -436,7 +453,7 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
436
453
|
}
|
|
437
454
|
|
|
438
455
|
endTextBlock()
|
|
439
|
-
|
|
456
|
+
endThinking()
|
|
440
457
|
|
|
441
458
|
stream.push({
|
|
442
459
|
type: "done",
|