Release 0.1.1

This commit is contained in:
Patrick Wozniak
2026-05-26 23:05:21 +02:00
parent 5d4277876b
commit cc05faa526
9 changed files with 140 additions and 25 deletions
+12
View File
@@ -0,0 +1,12 @@
# Changelog
## 0.1.1 - 2026-05-26
- Align Command Code generate requests with CLI `0.27.2` headers and payload shape.
- Support official Command Code CLI auth files using the `command-code` credential key.
- Handle `reasoning-start` and ignore streamed `tool-result` events.
- Cap generated `max_tokens` by the selected model and the Command Code output limit.
## 0.1.0 - 2026-05-05
- Initial public release.
+11
View File
@@ -72,6 +72,17 @@ Create `~/.commandcode/auth.json`:
}
```
The official Command Code CLI auth shape is also supported:
```json
{
"command-code": {
"type": "api",
"key": "user_..."
}
}
```
Or use pi's auth file at `~/.pi/agent/auth.json`:
```json
+2 -2
View File
@@ -15,7 +15,7 @@
import { calculateCost, createAssistantMessageEventStream } from "@mariozechner/pi-ai"
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"
import { createStreamCommandCode, DEFAULT_API_BASE } from "./src/core.ts"
import { COMMAND_CODE_CLI_VERSION, createStreamCommandCode, DEFAULT_API_BASE } from "./src/core.ts"
import { DEFAULT_MODELS_URL, fetchCommandCodeModels } from "./src/models.ts"
import { getApiKey, login, refreshToken } from "./src/oauth.ts"
@@ -43,7 +43,7 @@ export default async function (pi: ExtensionAPI) {
api: "commandcode-custom",
streamSimple: streamCommandCode,
headers: {
"x-command-code-version": "0.24.1",
"x-command-code-version": COMMAND_CODE_CLI_VERSION,
"x-cli-environment": "production",
},
oauth: {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "pi-commandcode-provider",
"version": "0.1.0",
"version": "0.1.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pi-commandcode-provider",
"version": "0.1.0",
"version": "0.1.1",
"license": "MIT",
"dependencies": {
"@mariozechner/pi-ai": "0.72.0"
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "pi-commandcode-provider",
"version": "0.1.0",
"version": "0.1.1",
"description": "pi custom provider for Command Code API (commandcode.ai)",
"type": "module",
"keywords": [
@@ -22,6 +22,7 @@
"index.ts",
"src/",
"README.md",
"CHANGELOG.md",
"LICENSE"
],
"scripts": {
+17 -7
View File
@@ -42,6 +42,16 @@ function defaultAuthPaths(home: string): string[] {
return [join(home, ".commandcode", "auth.json"), join(home, ".pi", "agent", "auth.json")]
}
function apiKeyFromCredentialRecord(value: unknown): string | undefined {
if (!isRecord(value)) return undefined
const type = stringValue(value.type)
if (type === "api") return stringValue(value.key)
if (type === "oauth") return stringValue(value.access)
return stringValue(value.key) ?? stringValue(value.access)
}
export function getApiKey(
options: {
env?: NodeJS.ProcessEnv
@@ -61,18 +71,18 @@ export function getApiKey(
const parsed: unknown = JSON.parse(readFileSync(authPath, "utf-8"))
if (!isRecord(parsed)) continue
// Legacy: direct apiKey or commandcode field
// Legacy: direct apiKey or commandcode field.
const apiKey = stringValue(parsed.apiKey)
if (apiKey) return apiKey
const commandcode = stringValue(parsed.commandcode)
if (commandcode) return commandcode
// OAuth: pi stores OAuth credentials as {"commandcode": {"type":"oauth","access":"...","refresh":"...","expires":...}}
const providerKey = isRecord(parsed.commandcode) ? parsed.commandcode : undefined
if (providerKey && stringValue(providerKey.type) === "oauth") {
const access = stringValue(providerKey.access)
if (access) return access
}
// pi stores OAuth credentials as {"commandcode": {"type":"oauth","access":"..."}}.
// The official Command Code CLI stores API credentials under "command-code".
const providerKey =
apiKeyFromCredentialRecord(parsed.commandcode) ??
apiKeyFromCredentialRecord(parsed["command-code"])
if (providerKey) return providerKey
} catch {
// Ignore malformed or unreadable auth files.
}
+42 -9
View File
@@ -38,6 +38,9 @@ export * from "./converters.ts"
export * from "./types.ts"
export const DEFAULT_API_BASE = "https://api.commandcode.ai"
export const COMMAND_CODE_CLI_VERSION = "0.27.2"
const DEFAULT_GENERATE_MAX_TOKENS = 64_000
function defaultUsage(): Usage {
return {
@@ -77,6 +80,23 @@ function successStopReason(reason: TerminalReason): StopReason {
return "stop"
}
function generateMaxTokens(model: ModelLike, options?: StreamOptions): number {
return Math.min(
options?.maxTokens ?? model.maxTokens,
model.maxTokens,
DEFAULT_GENERATE_MAX_TOKENS,
)
}
export function projectSlugFromPath(pathName: string): string {
const slug = pathName
.toLowerCase()
.replace(/^[a-z]:/i, "")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "")
return slug || "project"
}
export function createStreamCommandCode(deps: CoreDependencies) {
const apiBase = deps.apiBase ?? DEFAULT_API_BASE
const fetchImpl = deps.fetchImpl ?? fetch
@@ -235,7 +255,13 @@ export function createStreamCommandCode(deps: CoreDependencies) {
break
}
case "reasoning-start": {
endTextBlock()
break
}
case "reasoning-delta": {
endTextBlock()
thinkingBlock.push(stringValue(event.text) ?? "")
break
}
@@ -245,6 +271,10 @@ export function createStreamCommandCode(deps: CoreDependencies) {
break
}
case "tool-result": {
break
}
case "tool-call": {
endTextBlock()
const toolCall: ToolCallContent = {
@@ -303,9 +333,12 @@ export function createStreamCommandCode(deps: CoreDependencies) {
try {
stream.push({ type: "start", partial: output })
const workingDir = cwd()
const threadId = uuid()
let body: unknown = {
config: {
workingDir: cwd(),
workingDir,
date: new Date(now()).toISOString().split("T")[0],
environment: getEnvironmentInfo(),
structure: [],
@@ -315,18 +348,19 @@ export function createStreamCommandCode(deps: CoreDependencies) {
gitStatus: "",
recentCommits: [],
},
memory: "",
taste: "",
memory: null,
taste: null,
skills: null,
permissionMode: "standard",
params: {
model: model.id,
messages: messagesToCC(context.messages),
tools: toolsToJson(context.tools),
system: context.systemPrompt ?? "",
max_tokens: Math.min(options?.maxTokens ?? model.maxTokens, 200_000),
max_tokens: generateMaxTokens(model, options),
temperature: 0.3,
stream: true,
},
threadId,
}
const nextBody = await raceAbort(
@@ -341,12 +375,11 @@ export function createStreamCommandCode(deps: CoreDependencies) {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
"x-command-code-version": "0.24.1",
"x-command-code-version": COMMAND_CODE_CLI_VERSION,
"x-cli-environment": "production",
"x-project-slug": "pi-cc",
"x-taste-learning": "false",
"x-project-slug": projectSlugFromPath(workingDir),
"x-taste-learning": "true",
"x-co-flag": "false",
"x-session-id": uuid(),
...options?.headers,
},
body: JSON.stringify(body),
+23 -1
View File
@@ -15,6 +15,7 @@ import {
mapFinishReason,
messagesToCC,
parseStreamEventLine,
projectSlugFromPath,
textContent,
toJsonSchema,
toolsToJson,
@@ -27,12 +28,13 @@ describe("getApiKey()", () => {
assert.equal(getApiKey({ env: { COMMANDCODE_API_KEY: "env-key" }, authPaths: [] }), "env-key")
})
it("reads apiKey, commandcode, and pi OAuth credential fields from explicit auth paths", () => {
it("reads apiKey, commandcode, pi OAuth, and official CLI credential fields", () => {
const dir = mkdtempSync(join(tmpdir(), "cc-auth-"))
try {
const first = join(dir, "first.json")
const second = join(dir, "second.json")
const oauth = join(dir, "oauth.json")
const official = join(dir, "official.json")
writeFileSync(first, JSON.stringify({ apiKey: "file-key" }))
writeFileSync(second, JSON.stringify({ commandcode: "fallback-key" }))
writeFileSync(
@@ -46,9 +48,19 @@ describe("getApiKey()", () => {
},
}),
)
writeFileSync(
official,
JSON.stringify({
"command-code": {
type: "api",
key: "official-cli-key",
},
}),
)
assert.equal(getApiKey({ env: {}, authPaths: [first, second] }), "file-key")
assert.equal(getApiKey({ env: {}, authPaths: [second] }), "fallback-key")
assert.equal(getApiKey({ env: {}, authPaths: [oauth] }), "oauth-access-key")
assert.equal(getApiKey({ env: {}, authPaths: [official] }), "official-cli-key")
} finally {
rmSync(dir, { recursive: true, force: true })
}
@@ -78,6 +90,16 @@ describe("getApiKey()", () => {
})
})
describe("projectSlugFromPath()", () => {
it("matches the official CLI-style slug from an absolute working directory", () => {
assert.equal(
projectSlugFromPath("/Users/patwoz/dev/Personal/pi/pi-commandcode-provider"),
"users-patwoz-dev-personal-pi-pi-commandcode-provider",
)
assert.equal(projectSlugFromPath("/repo"), "repo")
})
})
describe("textContent()", () => {
it("extracts and joins text blocks", () => {
assert.equal(
+29 -3
View File
@@ -142,6 +142,7 @@ describe("streamCommandCode — successful streams", () => {
server.mockResponse({
type: "success",
events: [
JSON.stringify({ type: "reasoning-start" }),
JSON.stringify({ type: "reasoning-delta", text: "think" }),
JSON.stringify({ type: "reasoning-end" }),
JSON.stringify({ type: "text-delta", text: "Using tool" }),
@@ -244,7 +245,13 @@ describe("streamCommandCode — request serialization", () => {
assert.equal(objectAt(body, ["params", "model"]), "deepseek/deepseek-v4-flash")
assert.equal(objectAt(body, ["params", "stream"]), true)
assert.equal(objectAt(body, ["params", "max_tokens"]), 500)
assert.equal(objectAt(body, ["params", "temperature"]), 0.3)
assert.equal(objectAt(body, ["params", "system"]), "You are a test assistant.")
assert.equal(objectAt(body, ["memory"]), null)
assert.equal(objectAt(body, ["taste"]), null)
assert.equal(objectAt(body, ["skills"]), null)
assert.equal(objectAt(body, ["permissionMode"]), undefined)
assert.equal(objectAt(body, ["threadId"]), "00000000-0000-4000-8000-000000000000")
assert.equal(
objectAt(body, ["params", "messages", "1", "content", "0", "text"]),
"first response",
@@ -253,8 +260,11 @@ describe("streamCommandCode — request serialization", () => {
const headers = server.lastRequestHeaders()
assert.equal(headers.authorization, "Bearer mock-key")
assert.equal(headers["x-command-code-version"], "0.24.1")
assert.equal(headers["x-session-id"], "00000000-0000-4000-8000-000000000000")
assert.equal(headers["x-command-code-version"], "0.27.2")
assert.equal(headers["x-project-slug"], "repo")
assert.equal(headers["x-taste-learning"], "true")
assert.equal(headers["x-co-flag"], "false")
assert.equal(headers["x-session-id"], undefined)
})
it("caps maxTokens and passes custom headers", async () => {
@@ -272,10 +282,26 @@ describe("streamCommandCode — request serialization", () => {
}),
)
assert.equal(objectAt(server.lastRequestBody(), ["params", "max_tokens"]), 200_000)
assert.equal(objectAt(server.lastRequestBody(), ["params", "max_tokens"]), 64_000)
assert.equal(server.lastRequestHeaders()["x-custom"], "value")
})
it("caps default maxTokens by the selected model", async () => {
server.mockResponse({
type: "success",
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
})
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
await collectEvents(
streamCommandCode(makeModel({ maxTokens: 8_192 }), makeContext(), {
apiKey: "mock-key",
}),
)
assert.equal(objectAt(server.lastRequestBody(), ["params", "max_tokens"]), 8_192)
})
it("runs onPayload and onResponse hooks", async () => {
server.mockResponse({
type: "success",