pi-commandcode-provider 0.5.0 → 0.5.1
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 +9 -0
- package/README.md +2 -2
- package/index.ts +3 -7
- package/package.json +1 -1
- package/src/converters.ts +47 -12
- package/src/core.ts +5 -9
- package/src/models.ts +58 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.5.1 - 2026-08-11
|
|
6
|
+
|
|
7
|
+
- Add model-specific image input capabilities from the `command-code@1.15.1` catalog and forward user and tool-result images using the current Command Code wire format.
|
|
8
|
+
- Update the Command Code client version header to `1.15.1`.
|
|
9
|
+
|
|
10
|
+
### Contributors
|
|
11
|
+
|
|
12
|
+
- @DiyarD — reported missing vision support for GPT-5.6 Luna, Muse Spark 1.2, and other vision-capable models.
|
|
13
|
+
|
|
5
14
|
## 0.5.0 - 2026-08-07
|
|
6
15
|
|
|
7
16
|
- Stop replaying completed assistant reasoning traces to Command Code while preserving visible text and completed tool calls in follow-up request history.
|
package/README.md
CHANGED
|
@@ -132,9 +132,9 @@ The following environment variables are intended for tests, local mocks, and com
|
|
|
132
132
|
|
|
133
133
|
## Image input
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
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.15.1`; unknown models default to text-only until their upstream metadata is reviewed.
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
For vision-capable models, image blocks from user messages and tool results are forwarded in Command Code's current data-URL wire format. Text-only models reject image content before making a network request instead of silently dropping it.
|
|
138
138
|
|
|
139
139
|
## Pricing display
|
|
140
140
|
|
package/index.ts
CHANGED
|
@@ -15,16 +15,12 @@ import {
|
|
|
15
15
|
} from "@earendil-works/pi-coding-agent"
|
|
16
16
|
import { join } from "node:path"
|
|
17
17
|
|
|
18
|
-
import {
|
|
19
|
-
COMMAND_CODE_CLI_VERSION,
|
|
20
|
-
COMMAND_CODE_INPUT_TYPES,
|
|
21
|
-
createStreamCommandCode,
|
|
22
|
-
DEFAULT_API_BASE,
|
|
23
|
-
} from "./src/core.ts"
|
|
18
|
+
import { COMMAND_CODE_CLI_VERSION, createStreamCommandCode, DEFAULT_API_BASE } from "./src/core.ts"
|
|
24
19
|
import { calculateCommandCodeCost } from "./src/cost.ts"
|
|
25
20
|
import {
|
|
26
21
|
DEFAULT_MODELS_URL,
|
|
27
22
|
getModelsTimeoutMs,
|
|
23
|
+
inputModalitiesForModel,
|
|
28
24
|
loadCommandCodeModels,
|
|
29
25
|
thinkingMetadataForModel,
|
|
30
26
|
type CommandCodeModel,
|
|
@@ -75,7 +71,7 @@ function createProviderModel(model: {
|
|
|
75
71
|
name: model.name,
|
|
76
72
|
reasoning: model.reasoning,
|
|
77
73
|
...(thinkingMetadataForModel(model.id) ?? {}),
|
|
78
|
-
input:
|
|
74
|
+
input: inputModalitiesForModel(model.id),
|
|
79
75
|
cost: MODEL_COSTS[model.id] ?? ZERO_MODEL_COST,
|
|
80
76
|
contextWindow: model.contextWindow,
|
|
81
77
|
maxTokens: model.maxTokens,
|
package/package.json
CHANGED
package/src/converters.ts
CHANGED
|
@@ -55,26 +55,50 @@ function apiKeyFromCredentialRecord(value: unknown): string | undefined {
|
|
|
55
55
|
return stringValue(value.key) ?? stringValue(value.access)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function
|
|
59
|
-
if (isRecord(value)) return value.type === "image"
|
|
60
|
-
return recordArray(value).
|
|
58
|
+
function imageParts(value: unknown): readonly Record<string, unknown>[] {
|
|
59
|
+
if (isRecord(value)) return value.type === "image" ? [value] : []
|
|
60
|
+
return recordArray(value).filter((part) => part.type === "image")
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
function imageContentError(role: string): Error {
|
|
64
|
-
return new Error(
|
|
65
|
-
`Command Code does not support image content in ${role}; refusing to send it to avoid lossy handling`,
|
|
66
|
-
)
|
|
64
|
+
return new Error(`Selected Command Code model does not support image content in ${role}`)
|
|
67
65
|
}
|
|
68
66
|
|
|
69
67
|
export function assertTextOnlyMessages(messages?: readonly MessageLike[]): void {
|
|
70
68
|
for (const message of messages ?? []) {
|
|
71
|
-
if (
|
|
69
|
+
if (imageParts(message.content).length > 0) {
|
|
72
70
|
const role = message.role === "toolResult" ? "tool results" : `${message.role} messages`
|
|
73
71
|
throw imageContentError(role)
|
|
74
72
|
}
|
|
75
73
|
}
|
|
76
74
|
}
|
|
77
75
|
|
|
76
|
+
function imageToCommandCode(part: Record<string, unknown>): Record<string, string> {
|
|
77
|
+
const data = stringValue(part.data)
|
|
78
|
+
const mimeType = stringValue(part.mimeType)
|
|
79
|
+
if (!data || !mimeType)
|
|
80
|
+
throw new Error("Invalid image content: expected base64 data and mimeType")
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
type: "image",
|
|
84
|
+
image: `data:${mimeType};base64,${data}`,
|
|
85
|
+
mimeType,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function userContentToCommandCode(content: unknown, allowImages: boolean): unknown {
|
|
90
|
+
if (typeof content === "string") return content
|
|
91
|
+
|
|
92
|
+
return recordArray(content).flatMap((part) => {
|
|
93
|
+
if (part.type === "text") return [{ type: "text", text: stringValue(part.text) ?? "" }]
|
|
94
|
+
if (part.type === "image") {
|
|
95
|
+
if (!allowImages) throw imageContentError("user messages")
|
|
96
|
+
return [imageToCommandCode(part)]
|
|
97
|
+
}
|
|
98
|
+
return []
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
78
102
|
export function getApiKey(
|
|
79
103
|
options: {
|
|
80
104
|
env?: NodeJS.ProcessEnv
|
|
@@ -115,8 +139,6 @@ export function getApiKey(
|
|
|
115
139
|
}
|
|
116
140
|
|
|
117
141
|
export function textContent(message: { content?: unknown }): string {
|
|
118
|
-
if (hasImageContent(message.content)) throw imageContentError("tool results")
|
|
119
|
-
|
|
120
142
|
return recordArray(message.content)
|
|
121
143
|
.filter((part) => part.type === "text")
|
|
122
144
|
.map((part) => stringValue(part.text) ?? "")
|
|
@@ -157,8 +179,12 @@ function completeToolCallIds(messages?: readonly MessageLike[]): Set<string> {
|
|
|
157
179
|
return new Set([...callIds].filter((id) => resultIds.has(id)))
|
|
158
180
|
}
|
|
159
181
|
|
|
160
|
-
export function messagesToCC(
|
|
161
|
-
|
|
182
|
+
export function messagesToCC(
|
|
183
|
+
messages?: readonly MessageLike[],
|
|
184
|
+
options: { allowImages?: boolean } = {},
|
|
185
|
+
): unknown[] {
|
|
186
|
+
const allowImages = options.allowImages ?? false
|
|
187
|
+
if (!allowImages) assertTextOnlyMessages(messages)
|
|
162
188
|
|
|
163
189
|
const out: unknown[] = []
|
|
164
190
|
const pairedToolCallIds = completeToolCallIds(messages)
|
|
@@ -167,7 +193,7 @@ export function messagesToCC(messages?: readonly MessageLike[]): unknown[] {
|
|
|
167
193
|
if (message.role === "user") {
|
|
168
194
|
out.push({
|
|
169
195
|
role: "user",
|
|
170
|
-
content:
|
|
196
|
+
content: userContentToCommandCode(message.content, allowImages),
|
|
171
197
|
})
|
|
172
198
|
} else if (message.role === "assistant") {
|
|
173
199
|
const parts: unknown[] = []
|
|
@@ -201,6 +227,15 @@ export function messagesToCC(messages?: readonly MessageLike[]): unknown[] {
|
|
|
201
227
|
},
|
|
202
228
|
],
|
|
203
229
|
})
|
|
230
|
+
|
|
231
|
+
const images = imageParts(message.content)
|
|
232
|
+
if (images.length > 0) {
|
|
233
|
+
if (!allowImages) throw imageContentError("tool results")
|
|
234
|
+
out.push({
|
|
235
|
+
role: "user",
|
|
236
|
+
content: images.map(imageToCommandCode),
|
|
237
|
+
})
|
|
238
|
+
}
|
|
204
239
|
}
|
|
205
240
|
}
|
|
206
241
|
return out
|
package/src/core.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { randomUUID } from "node:crypto"
|
|
9
9
|
|
|
10
10
|
import { commandCodeErrorMessage, redactCommandCodeErrorText } from "./overflow.ts"
|
|
11
|
+
import { modelSupportsImageInput } from "./models.ts"
|
|
11
12
|
import {
|
|
12
13
|
getApiKey,
|
|
13
14
|
getEnvironmentInfo,
|
|
@@ -42,13 +43,7 @@ export * from "./overflow.ts"
|
|
|
42
43
|
export * from "./types.ts"
|
|
43
44
|
|
|
44
45
|
export const DEFAULT_API_BASE = "https://api.commandcode.ai"
|
|
45
|
-
export const COMMAND_CODE_CLI_VERSION = "
|
|
46
|
-
/**
|
|
47
|
-
* The legacy /alpha/generate request path used by this provider has no
|
|
48
|
-
* documented image-part contract. Keep the advertised capability text-only
|
|
49
|
-
* until Command Code documents and tests image handling for this endpoint.
|
|
50
|
-
*/
|
|
51
|
-
export const COMMAND_CODE_INPUT_TYPES = ["text"] as const
|
|
46
|
+
export const COMMAND_CODE_CLI_VERSION = "1.15.1"
|
|
52
47
|
|
|
53
48
|
const DEFAULT_GENERATE_MAX_TOKENS = 64_000
|
|
54
49
|
const DEFAULT_MAX_RETRIES = 0
|
|
@@ -472,7 +467,8 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
472
467
|
const reasoningEffort = mappedReasoningEffort(model, options)
|
|
473
468
|
const timeoutMs = options?.timeoutMs
|
|
474
469
|
|
|
475
|
-
|
|
470
|
+
const allowImages = modelSupportsImageInput(model.id)
|
|
471
|
+
if (!allowImages) assertTextOnlyMessages(context.messages)
|
|
476
472
|
|
|
477
473
|
let body: unknown = {
|
|
478
474
|
config: {
|
|
@@ -491,7 +487,7 @@ export function createStreamCommandCode(deps: CoreDependencies) {
|
|
|
491
487
|
skills: null,
|
|
492
488
|
params: {
|
|
493
489
|
model: model.id,
|
|
494
|
-
messages: messagesToCC(context.messages),
|
|
490
|
+
messages: messagesToCC(context.messages, { allowImages }),
|
|
495
491
|
tools: toolsToJson(context.tools),
|
|
496
492
|
system: systemPromptToText(context.systemPrompt),
|
|
497
493
|
max_tokens: generateMaxTokens(model, options),
|
package/src/models.ts
CHANGED
|
@@ -7,6 +7,63 @@ export const DEFAULT_MODELS_TIMEOUT_MS = 10_000
|
|
|
7
7
|
const DEFAULT_MAX_OUTPUT_TOKENS = 65_536
|
|
8
8
|
const MODEL_CACHE_VERSION = 1
|
|
9
9
|
|
|
10
|
+
export type CommandCodeInputType = "text" | "image"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Model input modalities from the command-code@1.15.1 bundled catalog.
|
|
14
|
+
* Models omitted here remain text-only so newly discovered IDs never claim
|
|
15
|
+
* image support without upstream evidence.
|
|
16
|
+
*/
|
|
17
|
+
export const MODEL_INPUT_MODALITIES: Readonly<Record<string, readonly CommandCodeInputType[]>> = {
|
|
18
|
+
"MiniMaxAI/MiniMax-M3": ["text", "image"],
|
|
19
|
+
"Qwen/Qwen3.6-Plus": ["text", "image"],
|
|
20
|
+
"Qwen/Qwen3.7-Flash": ["text", "image"],
|
|
21
|
+
"Qwen/Qwen3.7-Plus": ["text", "image"],
|
|
22
|
+
"Qwen/Qwen3.8-Max": ["text", "image"],
|
|
23
|
+
"claude-fable-5": ["text", "image"],
|
|
24
|
+
"claude-haiku-4-5-20251001": ["text", "image"],
|
|
25
|
+
"claude-opus-4-7": ["text", "image"],
|
|
26
|
+
"claude-opus-4-8": ["text", "image"],
|
|
27
|
+
"claude-opus-5": ["text", "image"],
|
|
28
|
+
"claude-sonnet-4-6": ["text", "image"],
|
|
29
|
+
"claude-sonnet-5": ["text", "image"],
|
|
30
|
+
"google/gemini-3.1-flash-lite": ["text", "image"],
|
|
31
|
+
"google/gemini-3.5-flash": ["text", "image"],
|
|
32
|
+
"google/gemini-3.5-flash-lite": ["text", "image"],
|
|
33
|
+
"google/gemini-3.6-flash": ["text", "image"],
|
|
34
|
+
"gpt-5.3-codex": ["text", "image"],
|
|
35
|
+
"gpt-5.4": ["text", "image"],
|
|
36
|
+
"gpt-5.4-mini": ["text", "image"],
|
|
37
|
+
"gpt-5.5": ["text", "image"],
|
|
38
|
+
"gpt-5.6-luna": ["text", "image"],
|
|
39
|
+
"gpt-5.6-sol": ["text", "image"],
|
|
40
|
+
"gpt-5.6-terra": ["text", "image"],
|
|
41
|
+
"meta/muse-spark-1.1": ["text", "image"],
|
|
42
|
+
"meta/muse-spark-1.2": ["text", "image"],
|
|
43
|
+
"meta/muse-spark-1.2-contributor": ["text", "image"],
|
|
44
|
+
"moonshotai/Kimi-K2.5": ["text", "image"],
|
|
45
|
+
"moonshotai/Kimi-K2.6": ["text", "image"],
|
|
46
|
+
"moonshotai/Kimi-K2.7-Code": ["text", "image"],
|
|
47
|
+
"moonshotai/Kimi-K2.7-Code-Highspeed": ["text", "image"],
|
|
48
|
+
"moonshotai/Kimi-K3": ["text", "image"],
|
|
49
|
+
"sakana/fugu-ultra": ["text", "image"],
|
|
50
|
+
"stepfun/Step-3.7-Flash": ["text", "image"],
|
|
51
|
+
"thinkingmachines/inkling": ["text", "image"],
|
|
52
|
+
"thinkingmachines/inkling-small": ["text", "image"],
|
|
53
|
+
"xai/grok-4.5": ["text", "image"],
|
|
54
|
+
"xiaomi/mimo-v2.5": ["text", "image"],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const TEXT_INPUT_ONLY = ["text"] as const
|
|
58
|
+
|
|
59
|
+
export function inputModalitiesForModel(modelId: string): readonly CommandCodeInputType[] {
|
|
60
|
+
return MODEL_INPUT_MODALITIES[modelId] ?? TEXT_INPUT_ONLY
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function modelSupportsImageInput(modelId: string): boolean {
|
|
64
|
+
return inputModalitiesForModel(modelId).includes("image")
|
|
65
|
+
}
|
|
66
|
+
|
|
10
67
|
export type PiThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
|
|
11
68
|
|
|
12
69
|
type CommandCodeReasoningEffort = Exclude<PiThinkingLevel, "off">
|
|
@@ -15,7 +72,7 @@ type CommandCodeReasoningEffort = Exclude<PiThinkingLevel, "off">
|
|
|
15
72
|
* Per-model reasoning efforts supported by Command Code's generate endpoint.
|
|
16
73
|
*
|
|
17
74
|
* The Provider API does not expose reasoning metadata. This is an exact
|
|
18
|
-
* snapshot of `reasoningEfforts` from the command-code@1.
|
|
75
|
+
* snapshot of `reasoningEfforts` from the command-code@1.15.1 model catalog
|
|
19
76
|
* (`packages/shared/src/model-catalog.ts`, also published in the generated
|
|
20
77
|
* `dist/bundled/command-code-knowledge/reference/models.md`). Models omitted
|
|
21
78
|
* here let Command Code choose their reasoning depth, matching the CLI.
|