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 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.
@@ -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's auth file at `~/.pi/agent/auth.json`:
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 within pi:
114
+ Any query will then use the Command Code API. You can list available models:
103
115
 
104
- ```txt
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
- ## Publish
139
+ ## Pricing
119
140
 
120
- ```sh
121
- npm login
122
- npm publish --access public
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 { calculateCost, createAssistantMessageEventStream } from "@mariozechner/pi-ai"
16
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"
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: createAssistantMessageEventStream,
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: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
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.1.1-next.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
- "@mariozechner/pi-ai": "0.72.0"
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 [join(home, ".commandcode", "auth.json"), join(home, ".pi", "agent", "auth.json")]
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
- options?.apiKey ??
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 ~/.pi/agent/auth.json.",
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 thinkingBlock: string[] = []
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 flushThinkingBlock = () => {
208
- if (thinkingBlock.length === 0) return
209
- const thinkingText = thinkingBlock.join("")
210
- thinkingBlock = []
211
- output.content.push({ type: "thinking", thinking: thinkingText })
212
- const idx = output.content.length - 1
213
- stream.push({
214
- type: "thinking_start",
215
- contentIndex: idx,
216
- partial: output,
217
- })
218
- stream.push({
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
- thinkingBlock.push(stringValue(event.text) ?? "")
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
- flushThinkingBlock()
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
- flushThinkingBlock()
456
+ endThinking()
440
457
 
441
458
  stream.push({
442
459
  type: "done",