Compare commits

...
10 Commits
Author SHA1 Message Date
cat-shark 8007a6480c fix(models): support DeepSeek V4.1 vision and max effort
CI / typecheck (push) Failing after 51s
CI / format (push) Failing after 4s
CI / Oh My Pi compatibility (push) Failing after 1m7s
CI / CodeQL SAST (push) Failing after 26m15s
CI / Semgrep — pi extension audit (push) Failing after 8m15s
CI / Gitleaks — secrets scan (push) Failing after 8s
CI / Dependency review (push) Skipped
CI / npm audit (push) Failing after 6s
CI / Check lifecycle scripts (push) Failing after 2s
Refresh the generated capability catalog from command-code@1.44.0 to
1.53.1. deepseek/deepseek-v4.1-flash now advertises image input and its
low, high, and max reasoning efforts, so pi forwards attached images and
exposes the full thinking-level selector instead of reporting the model
as text-only without selectable levels. The sync also adds gpt-6-astra,
xai/grok-4.6, and inclusionai/ling-3.0-flash-sante:free metadata.

Drop the now-obsolete manual Meta Muse Spark effort overrides because
upstream publishes efforts for meta/muse-spark-1.1 through 1.3-contributor
and MiniMaxAI/MiniMax-M3, and add a regression test for the V4.1
capabilities.
2026-09-13 22:27:51 +08:00
Patrick WozniakandGitHub adea6585c0 chore(release): publish 0.6.4 (#82) 2026-09-03 13:33:42 +02:00
8d26dbd374 feat(models): add reasoning support for meta/muse-spark-1.3 and 1.3-contributor (#80)
Muse Spark 1.3 and 1.3-contributor are discoverable via the Provider API
but Pi showed "Current model does not support thinking" because the
generated catalog had no reasoning flag or selectable efforts.

Reuse the existing Muse Spark manual override mechanism:
- add reasoning flags and image modalities for both ids to the generated
  catalog (CLI 1.40.1 snapshot follows the same pattern as 1.1/1.2)
- add a manual effort override of [minimal, low, medium, high, xhigh]
  in src/commandcode-catalog-overrides.ts, merged over the generated
  catalog at load time (same set as 1.1/1.2/1.2-contributor, no max)

Pi selected thinking levels now map to reasoning_effort via the
existing generic thinkingMetadataForModel / mappedReasoningEffort path;
no model-specific transport logic is needed.

Tests: typecheck, test-models (override merge + thinking metadata),
test-model-metadata-check, test-pure-functions, test-pricing, test-cost,
test-stream (reasoning_effort mapping), test-runtime, test-transport all pass.
No live Command Code API test was performed.

Co-authored-by: heie54 <14918606+heie54@user.noreply.gitee.com>
Co-authored-by: Patrick Wozniak <email@patwoz.de>
2026-09-03 13:30:09 +02:00
f9879a20ee chore(models): sync Command Code catalog (#81)
Co-authored-by: patlux <4481570+patlux@users.noreply.github.com>
2026-09-03 13:27:02 +02:00
Patrick WozniakandGitHub 3049ce1544 chore(release): publish 0.6.3 (#79) 2026-09-02 23:31:38 +02:00
9296d3dc31 fix(auth): stop the API key placeholder from shadowing Oh My Pi /login credentials (#78)
Oh My Pi kept the unresolved $COMMAND_CODE_API_KEY placeholder as a literal config API key that shadowed its /login credential store and was sent as the Bearer token (401). The placeholder is now registered only on pi, where it keeps the API-key auth method and --api-key working next to OAuth; on OMP the provider omits apiKey unless a real key is configured. Host-supplied placeholders are resolved or stripped on every stream path, and the legacy generate transport uses the same rule.

Stored /login OAuth and API-key credentials, --api-key, and env keys are now covered end to end on both pi and Oh My Pi, and CI runs the pi suite against a real binary.

Co-authored-by: ebreen <ebreen@users.noreply.github.com>
2026-09-02 22:43:23 +02:00
Patrick WozniakandGitHub 8416e76d9e Merge pull request #77 from patlux/release/0.6.2
chore(release): publish 0.6.2
2026-09-02 10:54:49 +02:00
Patrick Wozniak 245c352996 chore(release): prepare 0.6.2 2026-09-02 10:49:59 +02:00
Patrick WozniakandGitHub c60d39398a Merge pull request #76 from patlux/ci/bun-1.4
ci: run Bun 1.4.0 in the memory benchmark and omp jobs
2026-09-02 10:36:11 +02:00
Patrick Wozniak e1c0a024ce ci: run Bun 1.4.0 in the memory benchmark and omp jobs
Bump the memory benchmark runtime pins to the current stable releases:
Bun 1.3.11 -> 1.4.0, Node 22.19.0 -> 22.23.2, pi host 0.82.1 -> 0.84.4.
Pin the Oh My Pi compatibility job to the same Bun release instead of
'latest' so a required check does not drift on its own.
2026-09-02 10:33:18 +02:00
17 changed files with 481 additions and 76 deletions
+13 -3
View File
@@ -17,11 +17,21 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
cache: npm
- run: npm ci
- run: npm run typecheck
# `npm test` includes tests/test-pi-local.mjs, which drives a real pi
# binary against the mock API and otherwise skips silently.
- name: Install pi
run: |
npm install -g @earendil-works/pi-coding-agent@latest
echo "PI_BIN=$(npm prefix -g)/bin/pi" >> "$GITHUB_ENV"
- name: Verify pi starts
run: '"$PI_BIN" --version'
- run: npm test
env:
PI_LOCAL_REQUIRED: "1"
format:
runs-on: ubuntu-latest
@@ -46,9 +56,9 @@ jobs:
with:
node-version: 20
cache: npm
- uses: oven-sh/setup-bun@v2
- uses: oven-sh/setup-bun@v2.2.0
with:
bun-version: latest
bun-version: 1.4.0
- run: npm ci
- name: Install Oh My Pi
run: |
+3 -3
View File
@@ -17,9 +17,9 @@ jobs:
permissions:
contents: read
env:
PI_VERSION: 0.82.1
BUN_VERSION: 1.3.11
NODE_VERSION: 22.19.0
PI_VERSION: 0.84.4
BUN_VERSION: 1.4.0
NODE_VERSION: 22.23.2
MEMORY_BENCHMARK_RUNS: 6
MEMORY_BENCHMARK_WARMUP_MS: 2500
MEMORY_BENCHMARK_SAMPLES: 12
+28
View File
@@ -2,8 +2,36 @@
## Unreleased
- Refresh the generated Command Code capability catalog from `command-code@1.44.0` to `command-code@1.53.1`. `deepseek/deepseek-v4.1-flash` now advertises image input and its `low`, `high`, and `max` reasoning efforts, so pi forwards attached images and exposes the full thinking-level selector instead of the text-only, level-less defaults. New `gpt-6-astra`, `xai/grok-4.6`, and `inclusionai/ling-3.0-flash-sante:free` metadata comes along.
- Drop the now-obsolete manual Meta Muse Spark effort overrides: upstream ships efforts for `meta/muse-spark-1.1` through `1.3-contributor` and `MiniMaxAI/MiniMax-M3`, so `src/commandcode-catalog-overrides.ts` is empty.
## 0.6.4 - 2026-09-03
- Refresh the generated Command Code capability catalog from `command-code@1.40.1` to `command-code@1.44.0`, adding current image-input, reasoning, effort, and output-limit metadata for newly published models.
- Expose selectable thinking levels (`minimal`, `low`, `medium`, `high`, `xhigh`) for `meta/muse-spark-1.3` and `meta/muse-spark-1.3-contributor`, so Pi and Oh My Pi forward the selected `reasoning_effort` instead of keeping thinking disabled.
### Contributors
- @heie54 — added and validated Muse Spark 1.3 reasoning support (#80).
## 0.6.3 - 2026-09-02
- Fix Oh My Pi chat returning `401 Invalid 'Authorization' header` after `/login`: OMP kept the unresolved `$COMMAND_CODE_API_KEY` placeholder as a literal config API key that shadowed its stored credentials and was sent as the Bearer token. The placeholder is now registered only on pi, where it keeps the API-key login method and `--api-key` working next to OAuth; on OMP the provider omits `apiKey` unless a real key is configured. Placeholders passed by the host are also resolved or stripped on the Provider API and compat stream paths, and the legacy generate transport resolves its key through the same rule.
- Cover stored `/login` OAuth and API-key credentials, `--api-key`, and env keys end to end on both pi and Oh My Pi, asserting the exact Bearer token the mock API receives. CI now runs the pi end-to-end suite against a real `pi` binary instead of skipping it.
### Contributors
- @ebreen — reported and diagnosed the Oh My Pi `/login` 401, and opened the fix that this release builds on (#78).
## 0.6.2 - 2026-09-02
- Fix `omp plugin install` on Oh My Pi 18.x, which rejected 0.6.1 because its pi-ai lacks the `registerApiProvider` export; the compat registration now resolves at runtime and is skipped on hosts that register custom APIs themselves.
- Run the Oh My Pi compatibility suite against a real `omp` binary in CI as a required check, and assert there that the extension loads against OMP's bundled pi packages.
- Pin the CI memory benchmark and Oh My Pi jobs to Bun 1.4.0, Node 22.23.2, and pi 0.84.4.
### Contributors
- @AmeMizuki — reported the failing `omp plugin install` on Oh My Pi 18.1.2.
## 0.6.1 - 2026-09-01
+4
View File
@@ -52,6 +52,10 @@ COMMANDCODE_E2E_PROVIDER_API_KEY_FILE=/path/to/provider-key npm run test:e2e:liv
Use `npm run test:e2e:live:all` with the Go and GOAT file variables to run both subscription transports sequentially. Store keys in a secret manager and export each one to a new mode-`0600` temporary file for the test; never add key files to the repository. Direct `*_API_KEY` variables are intended primarily for protected CI secrets.
### pi end-to-end
`tests/test-pi-local.mjs` runs the extension inside a real `pi` binary against a mock Command Code API, including every credential source (`/login` OAuth and API-key credentials, `--api-key`, env keys). It skips locally when `pi` is not on `PATH`; CI installs pi and runs it as part of `npm test` with `PI_LOCAL_REQUIRED=1`. Point `PI_BIN` at another pi executable to test against a specific version.
### Oh My Pi compatibility
`tests/test-omp-compat.mjs` runs the extension inside a real `omp` binary against a mock Command Code API. It skips locally when `omp` is not on `PATH`; CI installs Oh My Pi and runs it as a required check with `OMP_COMPAT_REQUIRED=1`, so a change that only loads on pi fails CI instead of the next `omp plugin install`.
+4 -2
View File
@@ -41,6 +41,8 @@ Run `/login` in pi or OMP. Select **Use a subscription**, then **Command Code**.
If automatic transfer from the browser fails, copy the API key shown by Command Code and paste it into the terminal prompt.
On Oh My Pi, `/login` stores those credentials in OMP's credential store and chat uses them directly. If chat still returns `401 Invalid 'Authorization' header`, restart OMP after `/login` and confirm `/commandcode-quota` shows your account.
### Environment variable
```sh
@@ -86,7 +88,7 @@ Other extensions that stream with the active Command Code model, such as backgro
### Reasoning support
Reasoning capability and selectable effort levels follow the official CLI catalog independently. Models can therefore be marked as reasoning-capable even when Command Code chooses their depth automatically. Models with explicit effort support register a model-specific `thinkingLevelMap`, so pi and OMP expose only valid levels. For a few reasoning models the CLI catalog ships no effort levels although the endpoint accepts `reasoning_effort`; `src/commandcode-catalog-overrides.ts` adds a manual level set for those (currently `meta/muse-spark-1.1`, `meta/muse-spark-1.2`, and `meta/muse-spark-1.2-contributor`) on top of the generated catalog, and the tests fail once upstream publishes its own levels so the override gets removed. Pi's native OpenAI- and Anthropic-compatible providers translate the selected level for Provider API accounts; the existing Command Code generate transport sends the matching `reasoning_effort` for Go accounts.
Reasoning capability and selectable effort levels follow the official CLI catalog independently. Models can therefore be marked as reasoning-capable even when Command Code chooses their depth automatically. Models with explicit effort support register a model-specific `thinkingLevelMap`, so pi and OMP expose only valid levels, including the opt-in `xhigh` and `max` levels. `src/commandcode-catalog-overrides.ts` can add a manual level set for reasoning models that the CLI catalog ships without efforts; it is currently empty because upstream publishes efforts for every selectable model, and the tests fail once upstream publishes levels for a model that still has a manual override. Pi's native OpenAI- and Anthropic-compatible providers translate the selected level for Provider API accounts; the existing Command Code generate transport sends the matching `reasoning_effort` for Go accounts.
List Command Code models from the terminal:
@@ -137,7 +139,7 @@ The following environment variables are intended for tests, local mocks, and com
## Image input
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.40.1`; unknown models default to text-only until their upstream metadata is reviewed. A daily GitHub Actions job synchronizes the CLI version, image capabilities, reasoning flags, reasoning efforts, and model-specific output limits with the latest published CLI package and opens or updates a reviewable pull request when they change. Pricing remains manually reviewed because temporary promotions and long-context tiers require explicit review.
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.53.1`; unknown models default to text-only until their upstream metadata is reviewed. A daily GitHub Actions job synchronizes the CLI version, image capabilities, reasoning flags, reasoning efforts, and model-specific output limits with the latest published CLI package and opens or updates a reviewable pull request when they change. Pricing remains manually reviewed because temporary promotions and long-context tiers require explicit review.
For vision-capable models, Pi's native provider adapters forward image blocks from user messages and tool results using the documented OpenAI or Anthropic message schema. Unknown and text-only models remain marked text-only in Pi.
+42 -12
View File
@@ -17,6 +17,7 @@ import {
import { join } from "node:path"
import { getConfiguredApiKey } from "./src/api-key.ts"
import { pickCommandCodeApiKey, withResolvedCommandCodeApiKey } from "./src/converters.ts"
import { createStreamCommandCode } from "./src/core.ts"
import { calculateCommandCodeCost } from "./src/cost.ts"
import {
@@ -54,10 +55,40 @@ type CompatStreamFunction = (
* and registers custom APIs itself inside `registerProvider`. Resolve the
* function at runtime so the extension loads on both hosts.
*/
function registerCompatApiProvider(stream: CompatStreamFunction): void {
function compatApiProviderRegistrar(): ((...args: unknown[]) => unknown) | undefined {
const register = (piAiCompat as { registerApiProvider?: unknown }).registerApiProvider
if (typeof register !== "function") return
register({ api: COMMAND_CODE_API, stream, streamSimple: stream }, COMPAT_SOURCE_ID)
return typeof register === "function" ? (register as (...args: unknown[]) => unknown) : undefined
}
function registerCompatApiProvider(stream: CompatStreamFunction): void {
compatApiProviderRegistrar()?.(
{ api: COMMAND_CODE_API, stream, streamSimple: stream },
COMPAT_SOURCE_ID,
)
}
/**
* The `apiKey` handed to `registerProvider` means different things per host.
*
* pi parses `$COMMAND_CODE_API_KEY` as an env template: unresolved means
* "not configured", so `/login` credentials and `--api-key` take over, and
* the entry keeps the API-key auth method registered next to OAuth. Without
* it pi composes an OAuth-only provider and drops stored `api_key`
* credentials and `--api-key`.
*
* Oh My Pi has no template notion: an unresolved value stays a literal config
* override that shadows its `/login` credential store and is sent verbatim as
* `Authorization: Bearer $COMMAND_CODE_API_KEY`. There, omit `apiKey` unless
* a real key is configured; OMP then reads env keys and stored credentials
* itself.
*
* Hosts are told apart by the same `registerApiProvider` probe used for the
* compat registry: pi exports it, OMP does not.
*/
function providerApiKey(): string | undefined {
const configured = pickCommandCodeApiKey(getConfiguredApiKey(), undefined)
if (configured) return configured
return compatApiProviderRegistrar() ? "$COMMAND_CODE_API_KEY" : undefined
}
function commandCodeHeaders(): Record<string, string> | undefined {
@@ -76,7 +107,7 @@ function createProviderConfig(
return {
name: "Command Code",
baseUrl: apiBase,
apiKey: getConfiguredApiKey() ?? "$COMMAND_CODE_API_KEY",
apiKey: providerApiKey(),
api: COMMAND_CODE_API,
streamSimple: streamCommandCode,
headers,
@@ -132,15 +163,18 @@ export default async function (pi: ExtensionAPI) {
calculateCost: calculateCommandCodeCost,
apiBase: legacyApiBase(apiBase),
})
const resolveStreamOptions = (options?: Parameters<typeof streamNativeProvider>[2]) =>
withResolvedCommandCodeApiKey(options, getConfiguredApiKey())
const transport = createCommandCodeTransportRouter({
createStream: () => new AssistantMessageEventStream(),
streamProvider: (model, context, options) =>
streamNativeProvider(
{ ...model, api: apiForModelId(model.id), compat: model.compatConfig ?? model.compat },
context,
options,
resolveStreamOptions(options),
),
streamGenerate,
streamGenerate: (model, context, options) =>
streamGenerate(model, context, resolveStreamOptions(options)),
})
// pi dispatches the main chat through the registered provider, but sibling
@@ -149,13 +183,9 @@ export default async function (pi: ExtensionAPI) {
// api-registry, which knows nothing about extension providers. Register the
// custom api there so those calls reach the same transport. The registry
// resolves no credentials for extension providers, so fall back to the
// configured key when the caller passes none.
// configured key when the caller passes none or a placeholder.
const compatStream: CompatStreamFunction = (model, context, options) =>
transport.stream(
model,
context,
options?.apiKey ? options : { ...options, apiKey: getConfiguredApiKey() },
) as AssistantMessageEventStream
transport.stream(model, context, resolveStreamOptions(options)) as AssistantMessageEventStream
registerCompatApiProvider(compatStream)
pi.on("message_end", async (event, ctx) => {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "pi-commandcode-provider",
"version": "0.6.1",
"version": "0.6.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pi-commandcode-provider",
"version": "0.6.1",
"version": "0.6.4",
"license": "MIT",
"devDependencies": {
"@types/node": "25.6.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "pi-commandcode-provider",
"version": "0.6.1",
"version": "0.6.4",
"description": "pi custom provider for Command Code API (commandcode.ai)",
"type": "module",
"keywords": [
+3 -7
View File
@@ -11,13 +11,9 @@ import type { CommandCodeReasoningEffort } from "./commandcode-catalog.ts"
*
* Add a model only when the effort parameter is known to be accepted by the
* Command Code endpoint; remove it once the CLI catalog ships its own efforts.
* The map is currently empty: upstream published efforts for Meta Muse Spark
* 1.1-1.3 and MiniMax M3, so no manual policy is needed.
*/
export const MODEL_EFFORT_OVERRIDES: Readonly<
Record<string, readonly CommandCodeReasoningEffort[]>
> = {
// Meta Muse Spark: the CLI ships no effort levels, but the endpoint accepts
// `reasoning_effort` for these models and other hosts expose the same set.
"meta/muse-spark-1.1": ["minimal", "low", "medium", "high", "xhigh"],
"meta/muse-spark-1.2": ["minimal", "low", "medium", "high", "xhigh"],
"meta/muse-spark-1.2-contributor": ["minimal", "low", "medium", "high", "xhigh"],
}
> = {}
+28 -2
View File
@@ -1,10 +1,10 @@
export const COMMAND_CODE_CLI_VERSION = "1.40.1"
export const COMMAND_CODE_CLI_VERSION = "1.53.1"
export type CommandCodeInputType = "text" | "image"
export type CommandCodeReasoningEffort = "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
/**
* Generated from command-code@1.40.1 by `npm run sync:commandcode-catalog`.
* Generated from command-code@1.53.1 by `npm run sync:commandcode-catalog`.
* Do not edit manually.
*/
export const MODEL_INPUT_MODALITIES: Readonly<Record<string, readonly CommandCodeInputType[]>> = {
@@ -17,11 +17,13 @@ export const MODEL_INPUT_MODALITIES: Readonly<Record<string, readonly CommandCod
"claude-sonnet-4-6": ["text", "image"],
"claude-sonnet-5": ["text", "image"],
"deepseek/deepseek-v4-flash-vision-exp": ["text", "image"],
"deepseek/deepseek-v4.1-flash": ["text", "image"],
"google/gemini-3.1-flash-lite": ["text", "image"],
"google/gemini-3.5-flash": ["text", "image"],
"google/gemini-3.5-flash-lite": ["text", "image"],
"google/gemini-3.6-flash": ["text", "image"],
"google/gemini-3.7-flash": ["text", "image"],
"google/gemini-3.8-flash": ["text", "image"],
"gpt-5.3-codex": ["text", "image"],
"gpt-5.4": ["text", "image"],
"gpt-5.4-mini": ["text", "image"],
@@ -29,9 +31,12 @@ export const MODEL_INPUT_MODALITIES: Readonly<Record<string, readonly CommandCod
"gpt-5.6-luna": ["text", "image"],
"gpt-5.6-sol": ["text", "image"],
"gpt-5.6-terra": ["text", "image"],
"gpt-6-astra": ["text", "image"],
"meta/muse-spark-1.1": ["text", "image"],
"meta/muse-spark-1.2": ["text", "image"],
"meta/muse-spark-1.2-contributor": ["text", "image"],
"meta/muse-spark-1.3": ["text", "image"],
"meta/muse-spark-1.3-contributor": ["text", "image"],
"MiniMaxAI/MiniMax-M3": ["text", "image"],
"moonshotai/Kimi-K2.5": ["text", "image"],
"moonshotai/Kimi-K2.6": ["text", "image"],
@@ -44,11 +49,13 @@ export const MODEL_INPUT_MODALITIES: Readonly<Record<string, readonly CommandCod
"Qwen/Qwen3.8-27B": ["text", "image"],
"Qwen/Qwen3.8-Flash": ["text", "image"],
"Qwen/Qwen3.8-Max": ["text", "image"],
"Qwen/Qwen3.8-Max-0902": ["text", "image"],
"sakana/fugu-ultra": ["text", "image"],
"stepfun/Step-3.7-Flash": ["text", "image"],
"thinkingmachines/inkling": ["text", "image"],
"thinkingmachines/inkling-small": ["text", "image"],
"xai/grok-4.5": ["text", "image"],
"xai/grok-4.6": ["text", "image"],
"xiaomi/mimo-v2.5": ["text", "image"],
"z-ai/glm-5.3-flash": ["text", "image"],
}
@@ -65,11 +72,13 @@ export const MODEL_REASONING: Readonly<Record<string, true>> = {
"deepseek/deepseek-v4-flash-fast": true,
"deepseek/deepseek-v4-flash-vision-exp": true,
"deepseek/deepseek-v4-pro": true,
"deepseek/deepseek-v4.1-flash": true,
"google/gemini-3.1-flash-lite": true,
"google/gemini-3.5-flash": true,
"google/gemini-3.5-flash-lite": true,
"google/gemini-3.6-flash": true,
"google/gemini-3.7-flash": true,
"google/gemini-3.8-flash": true,
"gpt-5.3-codex": true,
"gpt-5.4": true,
"gpt-5.4-mini": true,
@@ -77,9 +86,14 @@ export const MODEL_REASONING: Readonly<Record<string, true>> = {
"gpt-5.6-luna": true,
"gpt-5.6-sol": true,
"gpt-5.6-terra": true,
"gpt-6-astra": true,
"inclusionai/ling-3.0-flash-sante:free": true,
"meituan/LongCat-2.0:free": true,
"meta/muse-spark-1.1": true,
"meta/muse-spark-1.2": true,
"meta/muse-spark-1.2-contributor": true,
"meta/muse-spark-1.3": true,
"meta/muse-spark-1.3-contributor": true,
"MiniMaxAI/MiniMax-M3": true,
"moonshotai/Kimi-K2.7-Code": true,
"moonshotai/Kimi-K2.7-Code-Highspeed": true,
@@ -94,6 +108,7 @@ export const MODEL_REASONING: Readonly<Record<string, true>> = {
"Qwen/Qwen3.8-27B": true,
"Qwen/Qwen3.8-Flash": true,
"Qwen/Qwen3.8-Max": true,
"Qwen/Qwen3.8-Max-0902": true,
"sakana/fugu-ultra": true,
"stepfun/Step-3.5-Flash": true,
"stepfun/Step-3.7-Flash": true,
@@ -120,11 +135,13 @@ export const MODEL_EFFORTS: Readonly<Record<string, readonly CommandCodeReasonin
"deepseek/deepseek-v4-flash-fast": ["low", "high", "max"],
"deepseek/deepseek-v4-flash-vision-exp": ["high", "max"],
"deepseek/deepseek-v4-pro": ["high", "max"],
"deepseek/deepseek-v4.1-flash": ["low", "high", "max"],
"google/gemini-3.1-flash-lite": ["low", "medium", "high"],
"google/gemini-3.5-flash": ["low", "medium", "high"],
"google/gemini-3.5-flash-lite": ["low", "medium", "high"],
"google/gemini-3.6-flash": ["low", "medium", "high"],
"google/gemini-3.7-flash": ["low", "medium", "high"],
"google/gemini-3.8-flash": ["low", "medium", "high"],
"gpt-5.3-codex": ["low", "medium", "high", "xhigh"],
"gpt-5.4": ["low", "medium", "high", "xhigh"],
"gpt-5.4-mini": ["low", "medium", "high"],
@@ -132,10 +149,18 @@ export const MODEL_EFFORTS: Readonly<Record<string, readonly CommandCodeReasonin
"gpt-5.6-luna": ["low", "medium", "high", "xhigh", "max"],
"gpt-5.6-sol": ["low", "medium", "high", "xhigh", "max"],
"gpt-5.6-terra": ["low", "medium", "high", "xhigh", "max"],
"gpt-6-astra": ["low", "medium", "high", "xhigh", "max"],
"meta/muse-spark-1.1": ["low", "medium", "high", "xhigh"],
"meta/muse-spark-1.2": ["low", "medium", "high", "xhigh"],
"meta/muse-spark-1.2-contributor": ["low", "medium", "high", "xhigh"],
"meta/muse-spark-1.3": ["low", "medium", "high", "xhigh", "max"],
"meta/muse-spark-1.3-contributor": ["low", "medium", "high", "xhigh"],
"MiniMaxAI/MiniMax-M3": ["low", "medium", "high"],
"moonshotai/Kimi-K3": ["low", "high", "max"],
"Qwen/Qwen3.8-27B": ["low", "medium", "xhigh"],
"Qwen/Qwen3.8-Flash": ["low", "medium", "xhigh"],
"Qwen/Qwen3.8-Max": ["low", "medium", "xhigh"],
"Qwen/Qwen3.8-Max-0902": ["low", "medium", "xhigh"],
"sakana/fugu-ultra": ["high", "xhigh"],
"tencent/hy4-preview": ["low", "medium", "high"],
"xai/grok-4.5": ["low", "medium", "high"],
@@ -146,6 +171,7 @@ export const MODEL_EFFORTS: Readonly<Record<string, readonly CommandCodeReasonin
}
export const MODEL_MAX_OUTPUT_TOKENS: Readonly<Record<string, number>> = {
"inclusionai/ling-3.0-flash-sante:free": 32_768,
"poolside/laguna-s-2.1-free": 32_768,
"Qwen/Qwen3.8-27B": 32_768,
"z-ai/glm-5.3-flash": 131_072,
+21 -4
View File
@@ -147,6 +147,13 @@ export const COMMAND_CODE_PLACEHOLDER_KEYS = new Set([
"COMMANDCODE_API_KEY",
])
function usableCommandCodeApiKey(value: string | undefined): string | undefined {
const trimmed = typeof value === "string" ? value.trim() : undefined
if (!trimmed) return undefined
if (COMMAND_CODE_PLACEHOLDER_KEYS.has(trimmed)) return undefined
return trimmed
}
/**
* Pick the real API key from a host registry value and/or the env/auth-file
* fallback, never returning a literal placeholder or an empty/whitespace value.
@@ -156,10 +163,20 @@ export function pickCommandCodeApiKey(
registryKey: string | undefined,
hostKey: string | undefined,
): string | undefined {
const trimmed = typeof registryKey === "string" ? registryKey.trim() : undefined
if (!trimmed) return hostKey
if (COMMAND_CODE_PLACEHOLDER_KEYS.has(trimmed)) return hostKey
return trimmed
return usableCommandCodeApiKey(registryKey) ?? usableCommandCodeApiKey(hostKey)
}
/**
* Replace a host-supplied placeholder (or missing key) with the configured
* fallback. Used for both registerProvider and the Provider API stream path.
*/
export function withResolvedCommandCodeApiKey<T extends { apiKey?: string }>(
options: T | undefined,
configuredKey: string | undefined,
): T | { apiKey?: string } {
const apiKey = pickCommandCodeApiKey(options?.apiKey, configuredKey)
if (options && apiKey === options.apiKey) return options
return { ...options, apiKey }
}
export function textContent(message: { content?: unknown }): string {
+5 -12
View File
@@ -19,6 +19,7 @@ import {
messagesToCC,
numberValue,
parseStreamEventLine,
pickCommandCodeApiKey,
recordOrEmpty,
stringValue,
toolsToJson,
@@ -237,22 +238,14 @@ export function createStreamCommandCode(deps: CoreDependencies) {
async function run() {
// Some hosts pass a literal env-var reference instead of resolving it.
const PLACEHOLDER_API_KEYS = new Set([
"$COMMAND_CODE_API_KEY",
"COMMAND_CODE_API_KEY",
"$COMMANDCODE_API_KEY",
"COMMANDCODE_API_KEY",
])
const hostKey =
options?.apiKey && !PLACEHOLDER_API_KEYS.has(options.apiKey) ? options.apiKey : undefined
const apiKey =
hostKey ??
const apiKey = pickCommandCodeApiKey(
options?.apiKey,
getApiKey({
env: deps.env,
authPaths: deps.authPaths,
homeDir: deps.homeDir,
})
}),
)
if (!apiKey) {
const msg: AssistantMessageLike = {
+16 -1
View File
@@ -126,6 +126,22 @@ describe("commandCodeModelsFromApiResponse()", () => {
assert.equal(modelSupportsImageInput("unknown-new-model"), false)
})
it("exposes DeepSeek V4.1 vision input and its low/high/max thinking levels", () => {
const modelId = "deepseek/deepseek-v4.1-flash"
assert.deepEqual(inputModalitiesForModel(modelId), ["text", "image"])
assert.equal(modelSupportsImageInput(modelId), true)
assert.equal(MODEL_REASONING[modelId], true)
assert.deepEqual(MODEL_EFFORTS[modelId], ["low", "high", "max"])
assert.deepEqual(thinkingMetadataForModel(modelId)?.thinkingLevelMap, {
minimal: null,
low: "low",
medium: null,
high: "high",
xhigh: null,
max: "max",
})
})
it("tracks reasoning independently from selectable effort levels", () => {
const reasoningModels = Object.keys(MODEL_REASONING)
const effortModels = Object.keys(MODEL_EFFORTS)
@@ -198,7 +214,6 @@ describe("commandCodeModelsFromApiResponse()", () => {
it("merges manual effort overrides over the generated catalog", () => {
const validEfforts = new Set(["minimal", "low", "medium", "high", "xhigh", "max"])
assert.ok(Object.keys(MODEL_EFFORT_OVERRIDES).length > 0)
for (const [modelId, efforts] of Object.entries(MODEL_EFFORT_OVERRIDES)) {
assert.equal(MODEL_REASONING[modelId], true, `${modelId} override needs a reasoning flag`)
assert.equal(
+144 -11
View File
@@ -9,7 +9,7 @@
*/
import assert from "node:assert/strict"
import { spawn } from "node:child_process"
import { spawn, spawnSync } from "node:child_process"
import { accessSync, constants, mkdtempSync, rmSync } from "node:fs"
import { createServer } from "node:http"
import { tmpdir } from "node:os"
@@ -176,19 +176,93 @@ const address = server.address()
const port = typeof address === "object" && address ? address.port : 0
const apiBase = `http://127.0.0.1:${port}`
function runOmp(args, timeoutMs = 30_000) {
const agentDir = join(tempHome, ".omp", "agent")
function ompEnv(overrides = {}) {
const env = {
...process.env,
HOME: tempHome,
USERPROFILE: tempHome,
PI_CODING_AGENT_DIR: agentDir,
COMMAND_CODE_API_KEY: "mock-key",
COMMANDCODE_API_BASE: `${apiBase}/provider/v1`,
COMMANDCODE_MODELS_URL: `${apiBase}/provider/v1/models`,
}
for (const [key, value] of Object.entries(overrides)) {
if (value === undefined) delete env[key]
else env[key] = value
}
return env
}
// Same DDL OMP 18 runs for its credential store; OMP's own migration is
// `CREATE TABLE IF NOT EXISTS`, so creating it first is safe.
const OMP_AUTH_CREDENTIALS_DDL = `CREATE TABLE IF NOT EXISTS auth_credentials (
id INTEGER PRIMARY KEY AUTOINCREMENT,
provider TEXT NOT NULL,
credential_type TEXT NOT NULL,
data TEXT NOT NULL,
disabled_cause TEXT DEFAULT NULL,
identity_key TEXT DEFAULT NULL,
created_at INTEGER NOT NULL DEFAULT (CAST(strftime('%s','now') AS INTEGER)),
updated_at INTEGER NOT NULL DEFAULT (CAST(strftime('%s','now') AS INTEGER))
)`
/**
* Store a credential the way OMP's `/login` does: in the `auth_credentials`
* table of `agent.db`. OMP is a Bun binary, so `bun:sqlite` is always
* available next to it and matches the SQLite build OMP itself uses.
* Pass `undefined` to remove any stored Command Code credential.
*/
function seedOmpCredential(credential) {
const rows =
credential === undefined
? []
: [
[
credential.type,
JSON.stringify(
credential.type === "oauth"
? {
access: credential.access,
refresh: credential.refresh,
expires: credential.expires,
}
: { key: credential.key, source: "login" },
),
],
]
const script = `
import { Database } from "bun:sqlite"
// \`bun -e\` has no script slot: argv is [bun, ...args].
const [dbPath, ddl, rowsJson] = process.argv.slice(1)
const db = new Database(dbPath)
db.run(ddl)
db.run("DELETE FROM auth_credentials WHERE provider = ?", ["commandcode"])
for (const [type, data] of JSON.parse(rowsJson)) {
db.run(
"INSERT INTO auth_credentials (provider, credential_type, data) VALUES (?, ?, ?)",
["commandcode", type, data],
)
}
db.close()
`
const result = spawnSync(
"bun",
["-e", script, join(agentDir, "agent.db"), OMP_AUTH_CREDENTIALS_DDL, JSON.stringify(rows)],
{ env: ompEnv(), encoding: "utf-8" },
)
assert.equal(result.status, 0, result.stderr)
}
function runOmp(args, timeoutOrOptions = 30_000) {
const options =
typeof timeoutOrOptions === "number" ? { timeoutMs: timeoutOrOptions } : timeoutOrOptions
const timeoutMs = options.timeoutMs ?? 30_000
return new Promise((resolve) => {
const child = spawn(OMP_BIN, args, {
cwd: PROJECT_DIR,
env: {
...process.env,
HOME: tempHome,
USERPROFILE: tempHome,
PI_CODING_AGENT_DIR: join(tempHome, ".omp", "agent"),
COMMAND_CODE_API_KEY: "mock-key",
COMMANDCODE_API_BASE: `${apiBase}/provider/v1`,
COMMANDCODE_MODELS_URL: `${apiBase}/provider/v1/models`,
},
env: ompEnv(options.env),
stdio: ["ignore", "pipe", "pipe"],
})
let stdout = ""
@@ -269,6 +343,65 @@ try {
assert.equal(lastRequestBody?.model, TEST_MODEL)
assert.ok(Array.isArray(lastRequestBody?.messages))
// OMP stores `/login` credentials in agent.db and consults them only when
// the extension does not install a config API key. main registered the
// unresolved `$COMMAND_CODE_API_KEY` placeholder, which OMP kept as a
// literal config override and sent as the Bearer token, so stored
// credentials never reached the request (401).
const chatArgs = ["-e", EXT_PATH, "-p", "say mock token", "--model", `commandcode/${TEST_MODEL}`]
const noEnvKey = { COMMAND_CODE_API_KEY: undefined, COMMANDCODE_API_KEY: undefined }
console.log("[omp-compat] stored /login OAuth credential is used when no env key exists")
seedOmpCredential({
type: "oauth",
access: "stored-oauth-token",
refresh: "stored-oauth-token",
expires: Date.now() + 24 * 60 * 60 * 1000,
})
requestCount = 0
lastRequestHeaders = {}
const oauthChat = await runOmp(chatArgs, { env: noEnvKey })
assert.equal(oauthChat.code, 0, oauthChat.stderr)
assert.match(oauthChat.stdout, /mock-omp-ok/)
assert.equal(requestCount, 1)
assert.equal(lastRequestHeaders.authorization, "Bearer stored-oauth-token")
console.log("[omp-compat] stored /login API key credential is used when no env key exists")
seedOmpCredential({ type: "api_key", key: "stored-api-key" })
requestCount = 0
lastRequestHeaders = {}
const apiKeyChat = await runOmp(chatArgs, { env: noEnvKey })
assert.equal(apiKeyChat.code, 0, apiKeyChat.stderr)
assert.match(apiKeyChat.stdout, /mock-omp-ok/)
assert.equal(requestCount, 1)
assert.equal(lastRequestHeaders.authorization, "Bearer stored-api-key")
console.log("[omp-compat] --api-key wins over a stored credential")
requestCount = 0
lastRequestHeaders = {}
const cliKeyChat = await runOmp([...chatArgs, "--api-key", "cli-key"], { env: noEnvKey })
assert.equal(cliKeyChat.code, 0, cliKeyChat.stderr)
assert.match(cliKeyChat.stdout, /mock-omp-ok/)
assert.equal(requestCount, 1)
assert.equal(lastRequestHeaders.authorization, "Bearer cli-key")
console.log("[omp-compat] COMMAND_CODE_API_KEY still works alongside a stored credential")
requestCount = 0
lastRequestHeaders = {}
const envKeyChat = await runOmp(chatArgs)
assert.equal(envKeyChat.code, 0, envKeyChat.stderr)
assert.match(envKeyChat.stdout, /mock-omp-ok/)
assert.equal(requestCount, 1)
assert.match(lastRequestHeaders.authorization ?? "", /^Bearer (mock-key|stored-api-key)$/)
console.log("[omp-compat] no credential at all never sends the placeholder")
seedOmpCredential(undefined)
requestCount = 0
lastRequestHeaders = {}
const noKeyChat = await runOmp(chatArgs, { env: noEnvKey })
assert.equal(requestCount, 0, JSON.stringify(lastRequestHeaders))
assert.doesNotMatch(noKeyChat.stdout + noKeyChat.stderr, /\$COMMAND_CODE_API_KEY/)
console.log("[omp-compat] developer advisory reaches the legacy generate request body")
requestCount = 0
requestBodies = []
+115 -16
View File
@@ -44,12 +44,20 @@ function findPiBinary() {
const PI_BIN = findPiBinary()
if (!PI_BIN) {
if (process.env.PI_LOCAL_REQUIRED === "1") {
console.error("[pi-local] FAIL - pi is required but not on PATH and PI_BIN is unset")
process.exit(1)
}
console.log("[pi-local] SKIP — pi is not on PATH")
process.exit(0)
}
const piCheck = spawnSync(PI_BIN, ["--help"], { stdio: "ignore" })
if (piCheck.error) {
if (process.env.PI_LOCAL_REQUIRED === "1") {
console.error(`[pi-local] FAIL - pi failed to start: ${piCheck.error.message}`)
process.exit(1)
}
console.log(`[pi-local] SKIP — pi failed to start: ${piCheck.error.message}`)
process.exit(0)
}
@@ -226,11 +234,19 @@ const env = {
COMMANDCODE_MODELS_URL: `${apiBase}/provider/v1/models`,
}
function runPi(args, timeoutMs = 30_000) {
function runPi(args, timeoutOrOptions = 30_000) {
const options =
typeof timeoutOrOptions === "number" ? { timeoutMs: timeoutOrOptions } : timeoutOrOptions
const timeoutMs = options.timeoutMs ?? 30_000
const childEnv = { ...env }
for (const [key, value] of Object.entries(options.env ?? {})) {
if (value === undefined) delete childEnv[key]
else childEnv[key] = value
}
return new Promise((resolve) => {
const child = spawn(PI_BIN, args, {
cwd: PROJECT_DIR,
env,
env: childEnv,
stdio: ["ignore", "pipe", "pipe"],
})
let stdout = ""
@@ -421,9 +437,9 @@ async function runRpcExtensionCommands(timeoutMs = 30_000) {
stderr += chunk.toString("utf-8")
})
const waitFor = (predicate) =>
const waitFor = (predicate, fromIndex = 0) =>
new Promise((resolve, reject) => {
const existing = events.find(predicate)
const existing = events.slice(fromIndex).find(predicate)
if (existing) {
resolve(existing)
return
@@ -445,17 +461,27 @@ async function runRpcExtensionCommands(timeoutMs = 30_000) {
)
const commandNames = commandsResponse.data?.commands?.map((command) => command.name) ?? []
send({ id: "status-before", type: "prompt", message: "/commandcode-status" })
await waitFor(
(event) => event.type === "response" && event.id === "status-before" && event.success,
)
const statusBefore = await waitFor(
(event) =>
event.type === "extension_ui_request" &&
event.method === "notify" &&
typeof event.message === "string" &&
event.message.includes("model count: 3"),
)
// The cached catalog registers immediately and refreshes in the
// background. `/commandcode-refresh` coalesces with an in-flight refresh,
// so the status must report the startup refresh as finished before the
// catalog is changed; otherwise the command reports the old catalog.
let statusBefore
for (let attempt = 0; attempt < 20; attempt += 1) {
const id = `status-before-${attempt}`
const fromIndex = events.length
send({ id, type: "prompt", message: "/commandcode-status" })
await waitFor((event) => event.type === "response" && event.id === id && event.success)
statusBefore = await waitFor(
(event) =>
event.type === "extension_ui_request" &&
event.method === "notify" &&
typeof event.message === "string" &&
event.message.includes("model count: 3"),
fromIndex,
)
if (/source: live[\s\S]*refresh: idle/.test(statusBefore.message)) break
await new Promise((resolve) => setTimeout(resolve, 100))
}
includeRefreshedModel = true
send({ id: "refresh", type: "prompt", message: "/commandcode-refresh" })
@@ -713,7 +739,11 @@ try {
const offlineListOutput = offlineList.stdout || offlineList.stderr
assert.match(offlineListOutput, /gpt-5\.4/)
assert.match(offlineListOutput, /cc-second-model/)
assert.match(offlineList.stderr, /Using the cached catalog/)
// The cached catalog is registered before the background refresh fails, and
// `--list-models` exits as soon as the list is printed. Whether the refresh
// warning reaches stderr first depends on the host runtime (Bun flushes it,
// Node does not), so the warning is asserted on the print run below, which
// waits for the response.
console.log("[pi-local] use a cached model while model discovery is offline")
requestCount = 0
@@ -824,6 +854,75 @@ try {
"string",
)
// pi resolves `/login` credentials, `--api-key`, and env keys through the
// provider's registered auth methods. Stored credentials and `--api-key`
// only reach the request when the provider keeps an API-key auth method
// next to OAuth, so every credential source is checked without an env key.
const authArgs = [
"--no-extensions",
"-e",
EXT_PATH,
"-p",
"say mock token",
"--provider",
"commandcode",
"--model",
TEST_MODEL,
]
const noEnvKey = { COMMAND_CODE_API_KEY: undefined, COMMANDCODE_API_KEY: undefined }
const authPath = join(agentDir, "auth.json")
console.log("[pi-local] stored /login OAuth credential is used when no env key exists")
writeFileSync(
authPath,
JSON.stringify({
commandcode: {
type: "oauth",
access: "stored-oauth-token",
refresh: "stored-oauth-token",
expires: Date.now() + 24 * 60 * 60 * 1000,
},
}),
)
requestCount = 0
lastRequestHeaders = {}
const oauthPrint = await runPi(authArgs, { env: noEnvKey })
assert.equal(oauthPrint.code, 0, oauthPrint.stderr)
assert.match(oauthPrint.stdout, /mock-pi-ok/)
assert.equal(requestCount, 1)
assert.equal(lastRequestHeaders.authorization, "Bearer stored-oauth-token")
console.log("[pi-local] stored /login API key credential is used when no env key exists")
writeFileSync(
authPath,
JSON.stringify({ commandcode: { type: "api_key", key: "stored-api-key" } }),
)
requestCount = 0
lastRequestHeaders = {}
const apiKeyPrint = await runPi(authArgs, { env: noEnvKey })
assert.equal(apiKeyPrint.code, 0, apiKeyPrint.stderr)
assert.match(apiKeyPrint.stdout, /mock-pi-ok/)
assert.equal(requestCount, 1)
assert.equal(lastRequestHeaders.authorization, "Bearer stored-api-key")
console.log("[pi-local] --api-key is used when no env key or stored credential exists")
rmSync(authPath, { force: true })
requestCount = 0
lastRequestHeaders = {}
const cliKeyPrint = await runPi([...authArgs, "--api-key", "cli-key"], { env: noEnvKey })
assert.equal(cliKeyPrint.code, 0, cliKeyPrint.stderr)
assert.match(cliKeyPrint.stdout, /mock-pi-ok/)
assert.equal(requestCount, 1)
assert.equal(lastRequestHeaders.authorization, "Bearer cli-key")
console.log("[pi-local] no credential at all never sends the placeholder")
requestCount = 0
lastRequestHeaders = {}
const noKeyPrint = await runPi(authArgs, { env: noEnvKey })
assert.notEqual(noKeyPrint.code, 0)
assert.equal(requestCount, 0, JSON.stringify(lastRequestHeaders))
assert.doesNotMatch(noKeyPrint.stdout + noKeyPrint.stderr, /\$COMMAND_CODE_API_KEY/)
console.log("[pi-local] Claude request through Anthropic Messages endpoint")
requestCount = 0
const claudePrint = await runPi(
+37
View File
@@ -17,6 +17,7 @@ import {
messagesToCC,
parseStreamEventLine,
pickCommandCodeApiKey,
withResolvedCommandCodeApiKey,
projectSlugFromPath,
textContent,
toJsonSchema,
@@ -148,6 +149,42 @@ describe("pickCommandCodeApiKey()", () => {
it("trims a real registry key", () => {
assert.equal(pickCommandCodeApiKey(" real-registry-key ", "file-key"), "real-registry-key")
})
it("never returns a placeholder as the host fallback", () => {
assert.equal(pickCommandCodeApiKey(undefined, "$COMMAND_CODE_API_KEY"), undefined)
assert.equal(pickCommandCodeApiKey("$COMMAND_CODE_API_KEY", "$COMMANDCODE_API_KEY"), undefined)
assert.equal(pickCommandCodeApiKey("COMMAND_CODE_API_KEY", "COMMANDCODE_API_KEY"), undefined)
})
it("omits a placeholder from registerProvider when no real key is configured", () => {
assert.equal(pickCommandCodeApiKey(undefined, undefined), undefined)
assert.equal(pickCommandCodeApiKey("$COMMAND_CODE_API_KEY", undefined), undefined)
assert.equal(pickCommandCodeApiKey("user_real-key", undefined), "user_real-key")
})
})
describe("withResolvedCommandCodeApiKey()", () => {
it("replaces a host placeholder with the configured key", () => {
assert.deepEqual(
withResolvedCommandCodeApiKey({ apiKey: "$COMMAND_CODE_API_KEY", extra: true }, "file-key"),
{ apiKey: "file-key", extra: true },
)
})
it("drops a placeholder when no configured key exists", () => {
assert.deepEqual(
withResolvedCommandCodeApiKey({ apiKey: "$COMMAND_CODE_API_KEY" }, undefined),
{
apiKey: undefined,
},
)
})
it("keeps a real host key and injects a configured key when the host omitted one", () => {
const options = { apiKey: "host-key" }
assert.equal(withResolvedCommandCodeApiKey(options, "file-key"), options)
assert.deepEqual(withResolvedCommandCodeApiKey(undefined, "file-key"), { apiKey: "file-key" })
})
})
describe("projectSlugFromPath()", () => {
+15
View File
@@ -108,6 +108,21 @@ describe("streamCommandCode — auth", () => {
assert.equal(server.lastRequestHeaders().authorization, "Bearer option-key")
})
it("treats a blank options.apiKey like a missing one", async () => {
server.mockResponse({
type: "success",
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
})
const { streamCommandCode } = createTestDeps({
apiBase: server.baseUrl(),
env: { COMMAND_CODE_API_KEY: "env-key" },
})
await collectEvents(streamCommandCode(makeModel(), makeContext(), { apiKey: " " }))
assert.equal(server.lastRequestHeaders().authorization, "Bearer env-key")
})
})
describe("streamCommandCode — successful streams", () => {