opencode-cmd-provider 0.1.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 +5 -0
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -0
- package/dist/src/env.d.ts +7 -0
- package/dist/src/env.js +24 -0
- package/dist/src/plugin/auth-server.d.ts +30 -0
- package/dist/src/plugin/auth-server.js +158 -0
- package/dist/src/plugin/auth.d.ts +6 -0
- package/dist/src/plugin/auth.js +38 -0
- package/dist/src/plugin/index.d.ts +6 -0
- package/dist/src/plugin/index.js +39 -0
- package/dist/src/plugin/models.d.ts +7 -0
- package/dist/src/plugin/models.js +50 -0
- package/dist/src/provider/aisdk-types.d.ts +9 -0
- package/dist/src/provider/aisdk-types.js +1 -0
- package/dist/src/provider/auth-key.d.ts +7 -0
- package/dist/src/provider/auth-key.js +65 -0
- package/dist/src/provider/command-code-model.d.ts +39 -0
- package/dist/src/provider/command-code-model.js +425 -0
- package/dist/src/provider/converters.d.ts +33 -0
- package/dist/src/provider/converters.js +256 -0
- package/dist/src/provider/cost.d.ts +19 -0
- package/dist/src/provider/cost.js +19 -0
- package/dist/src/provider/index.d.ts +5 -0
- package/dist/src/provider/index.js +9 -0
- package/dist/src/provider/json-schema.d.ts +1 -0
- package/dist/src/provider/json-schema.js +374 -0
- package/dist/src/provider/modalities.d.ts +9 -0
- package/dist/src/provider/modalities.js +53 -0
- package/dist/src/provider/models.d.ts +29 -0
- package/dist/src/provider/models.js +229 -0
- package/dist/src/provider/pricing.d.ts +24 -0
- package/dist/src/provider/pricing.js +188 -0
- package/dist/src/provider/project-slug.d.ts +1 -0
- package/dist/src/provider/project-slug.js +10 -0
- package/dist/src/provider/reasoning.d.ts +29 -0
- package/dist/src/provider/reasoning.js +74 -0
- package/dist/src/provider/redact.d.ts +2 -0
- package/dist/src/provider/redact.js +59 -0
- package/dist/src/provider/retry.d.ts +8 -0
- package/dist/src/provider/retry.js +83 -0
- package/dist/src/provider/stream.d.ts +5 -0
- package/dist/src/provider/stream.js +105 -0
- package/package.json +58 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/provider/modalities.ts — image input modality table (PLAN #5 Part B,
|
|
2
|
+
// port of pi's models.ts:17-55)
|
|
3
|
+
/**
|
|
4
|
+
* Model input modalities from the command-code@1.15.1 bundled catalog.
|
|
5
|
+
* Models omitted here remain text-only so newly discovered IDs never claim
|
|
6
|
+
* image support without upstream evidence.
|
|
7
|
+
*/
|
|
8
|
+
export const MODEL_INPUT_MODALITIES = {
|
|
9
|
+
"MiniMaxAI/MiniMax-M3": ["text", "image"],
|
|
10
|
+
"Qwen/Qwen3.6-Plus": ["text", "image"],
|
|
11
|
+
"Qwen/Qwen3.7-Flash": ["text", "image"],
|
|
12
|
+
"Qwen/Qwen3.7-Plus": ["text", "image"],
|
|
13
|
+
"Qwen/Qwen3.8-Max": ["text", "image"],
|
|
14
|
+
"claude-fable-5": ["text", "image"],
|
|
15
|
+
"claude-haiku-4-5-20251001": ["text", "image"],
|
|
16
|
+
"claude-opus-4-7": ["text", "image"],
|
|
17
|
+
"claude-opus-4-8": ["text", "image"],
|
|
18
|
+
"claude-opus-5": ["text", "image"],
|
|
19
|
+
"claude-sonnet-4-6": ["text", "image"],
|
|
20
|
+
"claude-sonnet-5": ["text", "image"],
|
|
21
|
+
"google/gemini-3.1-flash-lite": ["text", "image"],
|
|
22
|
+
"google/gemini-3.5-flash": ["text", "image"],
|
|
23
|
+
"google/gemini-3.5-flash-lite": ["text", "image"],
|
|
24
|
+
"google/gemini-3.6-flash": ["text", "image"],
|
|
25
|
+
"gpt-5.3-codex": ["text", "image"],
|
|
26
|
+
"gpt-5.4": ["text", "image"],
|
|
27
|
+
"gpt-5.4-mini": ["text", "image"],
|
|
28
|
+
"gpt-5.5": ["text", "image"],
|
|
29
|
+
"gpt-5.6-luna": ["text", "image"],
|
|
30
|
+
"gpt-5.6-sol": ["text", "image"],
|
|
31
|
+
"gpt-5.6-terra": ["text", "image"],
|
|
32
|
+
"meta/muse-spark-1.1": ["text", "image"],
|
|
33
|
+
"meta/muse-spark-1.2": ["text", "image"],
|
|
34
|
+
"meta/muse-spark-1.2-contributor": ["text", "image"],
|
|
35
|
+
"moonshotai/Kimi-K2.5": ["text", "image"],
|
|
36
|
+
"moonshotai/Kimi-K2.6": ["text", "image"],
|
|
37
|
+
"moonshotai/Kimi-K2.7-Code": ["text", "image"],
|
|
38
|
+
"moonshotai/Kimi-K2.7-Code-Highspeed": ["text", "image"],
|
|
39
|
+
"moonshotai/Kimi-K3": ["text", "image"],
|
|
40
|
+
"sakana/fugu-ultra": ["text", "image"],
|
|
41
|
+
"stepfun/Step-3.7-Flash": ["text", "image"],
|
|
42
|
+
"thinkingmachines/inkling": ["text", "image"],
|
|
43
|
+
"thinkingmachines/inkling-small": ["text", "image"],
|
|
44
|
+
"xai/grok-4.5": ["text", "image"],
|
|
45
|
+
"xiaomi/mimo-v2.5": ["text", "image"],
|
|
46
|
+
};
|
|
47
|
+
const TEXT_INPUT_ONLY = ["text"];
|
|
48
|
+
export function inputModalitiesForModel(modelId) {
|
|
49
|
+
return MODEL_INPUT_MODALITIES[modelId] ?? TEXT_INPUT_ONLY;
|
|
50
|
+
}
|
|
51
|
+
export function modelSupportsImageInput(modelId) {
|
|
52
|
+
return inputModalitiesForModel(modelId).includes("image");
|
|
53
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const DEFAULT_MODELS_URL = "https://api.commandcode.ai/provider/v1/models";
|
|
2
|
+
export declare const DEFAULT_MODELS_TIMEOUT_MS = 10000;
|
|
3
|
+
export interface CommandCodeModel {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
reasoning: boolean;
|
|
7
|
+
contextWindow: number;
|
|
8
|
+
maxTokens: number;
|
|
9
|
+
}
|
|
10
|
+
interface FetchCommandCodeModelsOptions {
|
|
11
|
+
url?: string;
|
|
12
|
+
fetchImpl?: typeof fetch;
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
}
|
|
16
|
+
interface LoadCommandCodeModelsOptions extends FetchCommandCodeModelsOptions {
|
|
17
|
+
cachePath: string;
|
|
18
|
+
}
|
|
19
|
+
export interface LoadCommandCodeModelsResult {
|
|
20
|
+
models: readonly CommandCodeModel[];
|
|
21
|
+
source: "live" | "cache" | "empty";
|
|
22
|
+
warning?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function getModelsTimeoutMs(env?: NodeJS.ProcessEnv): number;
|
|
25
|
+
export declare function commandCodeModelsFromApiResponse(value: unknown): readonly CommandCodeModel[];
|
|
26
|
+
export declare function commandCodeModelsFromCache(value: unknown): readonly CommandCodeModel[];
|
|
27
|
+
export declare function fetchCommandCodeModels(options?: FetchCommandCodeModelsOptions): Promise<readonly CommandCodeModel[]>;
|
|
28
|
+
export declare function loadCommandCodeModels(options: LoadCommandCodeModelsOptions): Promise<LoadCommandCodeModelsResult>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// src/provider/models.ts — model catalog fetch, parse, cache with offline
|
|
2
|
+
// fallback (PLAN #7, port of pi's models.ts)
|
|
3
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import { isReasoningModel } from "./reasoning.js";
|
|
6
|
+
export const DEFAULT_MODELS_URL = "https://api.commandcode.ai/provider/v1/models";
|
|
7
|
+
export const DEFAULT_MODELS_TIMEOUT_MS = 10_000;
|
|
8
|
+
const DEFAULT_MAX_OUTPUT_TOKENS = 65_536;
|
|
9
|
+
const MODEL_CACHE_VERSION = 1;
|
|
10
|
+
function isRecord(value) {
|
|
11
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
function stringField(record, key) {
|
|
14
|
+
const value = record[key];
|
|
15
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
16
|
+
throw new Error(`Expected ${key} to be a non-empty string`);
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function booleanField(record, key) {
|
|
21
|
+
const value = record[key];
|
|
22
|
+
if (typeof value !== "boolean")
|
|
23
|
+
throw new Error(`Expected ${key} to be a boolean`);
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function positiveNumberField(record, key) {
|
|
27
|
+
const value = record[key];
|
|
28
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
29
|
+
throw new Error(`Expected ${key} to be a positive number`);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function parseApiModel(value) {
|
|
34
|
+
if (!isRecord(value))
|
|
35
|
+
throw new Error("Expected model entry to be an object");
|
|
36
|
+
return {
|
|
37
|
+
id: stringField(value, "id"),
|
|
38
|
+
name: stringField(value, "name"),
|
|
39
|
+
contextLength: positiveNumberField(value, "context_length"),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function parseCachedModel(value) {
|
|
43
|
+
if (!isRecord(value))
|
|
44
|
+
throw new Error("Expected cached model entry to be an object");
|
|
45
|
+
const id = stringField(value, "id");
|
|
46
|
+
booleanField(value, "reasoning");
|
|
47
|
+
return {
|
|
48
|
+
id,
|
|
49
|
+
name: stringField(value, "name"),
|
|
50
|
+
reasoning: isReasoningModel(id),
|
|
51
|
+
contextWindow: positiveNumberField(value, "contextWindow"),
|
|
52
|
+
maxTokens: positiveNumberField(value, "maxTokens"),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function requireModels(models) {
|
|
56
|
+
if (models.length === 0)
|
|
57
|
+
throw new Error("Command Code returned an empty model catalog");
|
|
58
|
+
return models;
|
|
59
|
+
}
|
|
60
|
+
function errorMessage(error) {
|
|
61
|
+
return error instanceof Error ? error.message : String(error);
|
|
62
|
+
}
|
|
63
|
+
function abortError(reason) {
|
|
64
|
+
if (reason instanceof Error)
|
|
65
|
+
return reason;
|
|
66
|
+
return new DOMException("The operation was aborted", "AbortError");
|
|
67
|
+
}
|
|
68
|
+
function configuredTimeoutMs(timeoutMs) {
|
|
69
|
+
return timeoutMs !== undefined && Number.isFinite(timeoutMs) && timeoutMs > 0
|
|
70
|
+
? timeoutMs
|
|
71
|
+
: DEFAULT_MODELS_TIMEOUT_MS;
|
|
72
|
+
}
|
|
73
|
+
export function getModelsTimeoutMs(env = process.env) {
|
|
74
|
+
const raw = env.COMMANDCODE_MODELS_TIMEOUT_MS;
|
|
75
|
+
if (!raw)
|
|
76
|
+
return DEFAULT_MODELS_TIMEOUT_MS;
|
|
77
|
+
const parsed = Number(raw);
|
|
78
|
+
return configuredTimeoutMs(parsed);
|
|
79
|
+
}
|
|
80
|
+
class ModelDiscoveryTimeoutError extends Error {
|
|
81
|
+
constructor(timeoutMs) {
|
|
82
|
+
super(`Command Code model discovery timed out after ${timeoutMs}ms`);
|
|
83
|
+
this.name = "ModelDiscoveryTimeoutError";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function runWithTimeout(operation, timeoutMs, externalSignal) {
|
|
87
|
+
const controller = new AbortController();
|
|
88
|
+
let timer;
|
|
89
|
+
let settled = false;
|
|
90
|
+
let onExternalAbort;
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
const cleanup = () => {
|
|
93
|
+
if (timer !== undefined)
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
if (onExternalAbort && externalSignal) {
|
|
96
|
+
externalSignal.removeEventListener("abort", onExternalAbort);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const resolveOnce = (value) => {
|
|
100
|
+
if (settled)
|
|
101
|
+
return;
|
|
102
|
+
settled = true;
|
|
103
|
+
cleanup();
|
|
104
|
+
resolve(value);
|
|
105
|
+
};
|
|
106
|
+
const rejectOnce = (error) => {
|
|
107
|
+
if (settled)
|
|
108
|
+
return;
|
|
109
|
+
settled = true;
|
|
110
|
+
cleanup();
|
|
111
|
+
reject(error);
|
|
112
|
+
};
|
|
113
|
+
const abort = (reason) => {
|
|
114
|
+
const error = abortError(reason);
|
|
115
|
+
controller.abort(error);
|
|
116
|
+
rejectOnce(error);
|
|
117
|
+
};
|
|
118
|
+
if (externalSignal?.aborted) {
|
|
119
|
+
abort(externalSignal.reason);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
onExternalAbort = () => abort(externalSignal?.reason);
|
|
123
|
+
externalSignal?.addEventListener("abort", onExternalAbort, { once: true });
|
|
124
|
+
timer = setTimeout(() => abort(new ModelDiscoveryTimeoutError(timeoutMs)), timeoutMs);
|
|
125
|
+
Promise.resolve()
|
|
126
|
+
.then(() => operation(controller.signal))
|
|
127
|
+
.then(resolveOnce, rejectOnce);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
export function commandCodeModelsFromApiResponse(value) {
|
|
131
|
+
if (!isRecord(value))
|
|
132
|
+
throw new Error("Expected models response to be an object");
|
|
133
|
+
if (value.object !== "list")
|
|
134
|
+
throw new Error("Expected models response object to be 'list'");
|
|
135
|
+
const data = value.data;
|
|
136
|
+
if (!Array.isArray(data))
|
|
137
|
+
throw new Error("Expected models response data to be an array");
|
|
138
|
+
return data.map(parseApiModel).map((model) => ({
|
|
139
|
+
id: model.id,
|
|
140
|
+
name: `${model.name} (CC)`,
|
|
141
|
+
reasoning: isReasoningModel(model.id),
|
|
142
|
+
contextWindow: model.contextLength,
|
|
143
|
+
maxTokens: Math.min(model.contextLength, DEFAULT_MAX_OUTPUT_TOKENS),
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
export function commandCodeModelsFromCache(value) {
|
|
147
|
+
if (!isRecord(value))
|
|
148
|
+
throw new Error("Expected model cache to be an object");
|
|
149
|
+
if (value.version !== MODEL_CACHE_VERSION) {
|
|
150
|
+
throw new Error(`Expected model cache version ${MODEL_CACHE_VERSION}`);
|
|
151
|
+
}
|
|
152
|
+
if (!Array.isArray(value.models))
|
|
153
|
+
throw new Error("Expected cached models to be an array");
|
|
154
|
+
return requireModels(value.models.map(parseCachedModel));
|
|
155
|
+
}
|
|
156
|
+
export async function fetchCommandCodeModels(options = {}) {
|
|
157
|
+
const url = options.url ?? DEFAULT_MODELS_URL;
|
|
158
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
159
|
+
const body = await runWithTimeout(async (signal) => {
|
|
160
|
+
const response = await fetchImpl(url, {
|
|
161
|
+
headers: {
|
|
162
|
+
accept: "application/json",
|
|
163
|
+
},
|
|
164
|
+
signal,
|
|
165
|
+
});
|
|
166
|
+
if (!response.ok) {
|
|
167
|
+
throw new Error(`Failed to fetch Command Code models: ${response.status} ${response.statusText}`);
|
|
168
|
+
}
|
|
169
|
+
return await response.json();
|
|
170
|
+
}, configuredTimeoutMs(options.timeoutMs), options.signal);
|
|
171
|
+
return requireModels(commandCodeModelsFromApiResponse(body));
|
|
172
|
+
}
|
|
173
|
+
async function readCommandCodeModelsCache(cachePath) {
|
|
174
|
+
const contents = await readFile(cachePath, "utf-8");
|
|
175
|
+
const parsed = JSON.parse(contents);
|
|
176
|
+
return commandCodeModelsFromCache(parsed);
|
|
177
|
+
}
|
|
178
|
+
async function writeCommandCodeModelsCache(cachePath, models) {
|
|
179
|
+
await mkdir(dirname(cachePath), { recursive: true });
|
|
180
|
+
const temporaryPath = `${cachePath}.${process.pid}.tmp`;
|
|
181
|
+
try {
|
|
182
|
+
await writeFile(temporaryPath, `${JSON.stringify({ version: MODEL_CACHE_VERSION, models }, null, 2)}\n`, { encoding: "utf-8", mode: 0o600 });
|
|
183
|
+
await rename(temporaryPath, cachePath);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
try {
|
|
187
|
+
await rm(temporaryPath, { force: true });
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
// Best-effort cleanup must not hide the original cache write error.
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
export async function loadCommandCodeModels(options) {
|
|
195
|
+
const cachePath = options.cachePath;
|
|
196
|
+
try {
|
|
197
|
+
const models = await fetchCommandCodeModels(options);
|
|
198
|
+
try {
|
|
199
|
+
await writeCommandCodeModelsCache(cachePath, models);
|
|
200
|
+
return { models, source: "live" };
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
return {
|
|
204
|
+
models,
|
|
205
|
+
source: "live",
|
|
206
|
+
warning: `Loaded the live Command Code model catalog but could not update ${cachePath}: ${errorMessage(error)}`,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (liveError) {
|
|
211
|
+
if (options.signal?.aborted)
|
|
212
|
+
throw abortError(options.signal.reason ?? liveError);
|
|
213
|
+
try {
|
|
214
|
+
const models = await readCommandCodeModelsCache(cachePath);
|
|
215
|
+
return {
|
|
216
|
+
models,
|
|
217
|
+
source: "cache",
|
|
218
|
+
warning: `Could not refresh the Command Code model catalog (${errorMessage(liveError)}). Using the cached catalog from ${cachePath}.`,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
catch (cacheError) {
|
|
222
|
+
return {
|
|
223
|
+
models: [],
|
|
224
|
+
source: "empty",
|
|
225
|
+
warning: `Could not refresh the Command Code model catalog (${errorMessage(liveError)}), and no valid cached catalog is available at ${cachePath} (${errorMessage(cacheError)}). Command Code models will remain unavailable until /commandcode-refresh succeeds.`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface CommandCodeModelCostRates {
|
|
2
|
+
input: number;
|
|
3
|
+
output: number;
|
|
4
|
+
cacheRead: number;
|
|
5
|
+
cacheWrite: number;
|
|
6
|
+
}
|
|
7
|
+
export interface CommandCodeModelCostTier extends CommandCodeModelCostRates {
|
|
8
|
+
inputTokensAbove: number;
|
|
9
|
+
}
|
|
10
|
+
export interface CommandCodeModelCost extends CommandCodeModelCostRates {
|
|
11
|
+
tiers?: readonly CommandCodeModelCostTier[];
|
|
12
|
+
}
|
|
13
|
+
export declare const PRICING_SOURCE_URL = "https://commandcode.ai/docs/resources/pricing-limits";
|
|
14
|
+
export declare const PRICING_LAST_VERIFIED = "2026-08-04";
|
|
15
|
+
export declare const ZERO_MODEL_COST: CommandCodeModelCost;
|
|
16
|
+
/**
|
|
17
|
+
* Display prices in USD per million tokens.
|
|
18
|
+
*
|
|
19
|
+
* Context-dependent rates use pi's request-wide input pricing tiers. The
|
|
20
|
+
* highest threshold exceeded by input + cache reads + cache writes applies to
|
|
21
|
+
* the full request. The Command Code usage page remains authoritative for the
|
|
22
|
+
* amount billed for an individual request.
|
|
23
|
+
*/
|
|
24
|
+
export declare const MODEL_COSTS: Readonly<Record<string, CommandCodeModelCost>>;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// src/provider/pricing.ts — Command Code model pricing table (PLAN #6, port of
|
|
2
|
+
// pi's pricing.ts)
|
|
3
|
+
export const PRICING_SOURCE_URL = "https://commandcode.ai/docs/resources/pricing-limits";
|
|
4
|
+
export const PRICING_LAST_VERIFIED = "2026-08-04";
|
|
5
|
+
export const ZERO_MODEL_COST = {
|
|
6
|
+
input: 0,
|
|
7
|
+
output: 0,
|
|
8
|
+
cacheRead: 0,
|
|
9
|
+
cacheWrite: 0,
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Display prices in USD per million tokens.
|
|
13
|
+
*
|
|
14
|
+
* Context-dependent rates use pi's request-wide input pricing tiers. The
|
|
15
|
+
* highest threshold exceeded by input + cache reads + cache writes applies to
|
|
16
|
+
* the full request. The Command Code usage page remains authoritative for the
|
|
17
|
+
* amount billed for an individual request.
|
|
18
|
+
*/
|
|
19
|
+
export const MODEL_COSTS = {
|
|
20
|
+
// Free models
|
|
21
|
+
"poolside/laguna-s-2.1-free": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
22
|
+
"inclusionai/ling-3.0-flash-free": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
23
|
+
// Open and open-weight models
|
|
24
|
+
"tencent/hy3-paid": { input: 0.14, output: 0.58, cacheRead: 0.035, cacheWrite: 0 },
|
|
25
|
+
"moonshotai/Kimi-K3": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 0 },
|
|
26
|
+
"moonshotai/Kimi-K2.7-Code": { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0 },
|
|
27
|
+
"moonshotai/Kimi-K2.7-Code-Highspeed": {
|
|
28
|
+
input: 1.9,
|
|
29
|
+
output: 8,
|
|
30
|
+
cacheRead: 0.38,
|
|
31
|
+
cacheWrite: 0,
|
|
32
|
+
},
|
|
33
|
+
"moonshotai/Kimi-K2.6": { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0 },
|
|
34
|
+
"moonshotai/Kimi-K2.5": { input: 0.6, output: 3, cacheRead: 0.1, cacheWrite: 0 },
|
|
35
|
+
"zai-org/GLM-5.2": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
|
36
|
+
"zai-org/GLM-5.2-Fast": { input: 3, output: 10.25, cacheRead: 0.5, cacheWrite: 0 },
|
|
37
|
+
"zai-org/GLM-5.1": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
|
38
|
+
"zai-org/GLM-5": { input: 1, output: 3.2, cacheRead: 0.2, cacheWrite: 0 },
|
|
39
|
+
"MiniMaxAI/MiniMax-M3": { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
|
|
40
|
+
"MiniMaxAI/MiniMax-M2.7": { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
|
|
41
|
+
"MiniMaxAI/MiniMax-M2.5": { input: 0.3, output: 1.2, cacheRead: 0.03, cacheWrite: 0 },
|
|
42
|
+
// Permanent 75% discount.
|
|
43
|
+
"deepseek/deepseek-v4-pro": {
|
|
44
|
+
input: 0.435,
|
|
45
|
+
output: 0.87,
|
|
46
|
+
cacheRead: 0.003625,
|
|
47
|
+
cacheWrite: 0,
|
|
48
|
+
},
|
|
49
|
+
"deepseek/deepseek-v4-flash": {
|
|
50
|
+
input: 0.14,
|
|
51
|
+
output: 0.28,
|
|
52
|
+
cacheRead: 0.0028,
|
|
53
|
+
cacheWrite: 0,
|
|
54
|
+
},
|
|
55
|
+
"Qwen/Qwen3.8-Max": { input: 2, output: 6, cacheRead: 0.25, cacheWrite: 2.5 },
|
|
56
|
+
"Qwen/Qwen3.7-Max": { input: 2.5, output: 7.5, cacheRead: 0.5, cacheWrite: 3.13 },
|
|
57
|
+
"Qwen/Qwen3.7-Plus": {
|
|
58
|
+
input: 0.4,
|
|
59
|
+
output: 1.6,
|
|
60
|
+
cacheRead: 0.08,
|
|
61
|
+
cacheWrite: 0.5,
|
|
62
|
+
tiers: [
|
|
63
|
+
{
|
|
64
|
+
inputTokensAbove: 256_000,
|
|
65
|
+
input: 1.2,
|
|
66
|
+
output: 4.8,
|
|
67
|
+
cacheRead: 0.24,
|
|
68
|
+
cacheWrite: 1.5,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
"Qwen/Qwen3.7-Flash": {
|
|
73
|
+
input: 0.03,
|
|
74
|
+
output: 0.13,
|
|
75
|
+
cacheRead: 0.006,
|
|
76
|
+
cacheWrite: 0.038,
|
|
77
|
+
tiers: [
|
|
78
|
+
{
|
|
79
|
+
inputTokensAbove: 32_000,
|
|
80
|
+
input: 0.1,
|
|
81
|
+
output: 0.4,
|
|
82
|
+
cacheRead: 0.02,
|
|
83
|
+
cacheWrite: 0.125,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
inputTokensAbove: 256_000,
|
|
87
|
+
input: 0.2,
|
|
88
|
+
output: 0.8,
|
|
89
|
+
cacheRead: 0.04,
|
|
90
|
+
cacheWrite: 0.25,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
"Qwen/Qwen3.6-Max-Preview": {
|
|
95
|
+
input: 1.3,
|
|
96
|
+
output: 7.8,
|
|
97
|
+
cacheRead: 0.26,
|
|
98
|
+
cacheWrite: 1.63,
|
|
99
|
+
},
|
|
100
|
+
"Qwen/Qwen3.6-Plus": { input: 0.5, output: 3, cacheRead: 0.1, cacheWrite: 0 },
|
|
101
|
+
"stepfun/Step-3.7-Flash": { input: 0.2, output: 1.15, cacheRead: 0.04, cacheWrite: 0 },
|
|
102
|
+
"stepfun/Step-3.5-Flash": { input: 0.1, output: 0.3, cacheRead: 0.02, cacheWrite: 0 },
|
|
103
|
+
// Permanent discounted rates.
|
|
104
|
+
"xiaomi/mimo-v2.5-pro": { input: 0.435, output: 0.87, cacheRead: 0.0036, cacheWrite: 0 },
|
|
105
|
+
"xiaomi/mimo-v2.5": { input: 0.14, output: 0.28, cacheRead: 0.0028, cacheWrite: 0 },
|
|
106
|
+
"nvidia/nemotron-3-ultra-550b-a55b": {
|
|
107
|
+
input: 0.6,
|
|
108
|
+
output: 2.4,
|
|
109
|
+
cacheRead: 0.12,
|
|
110
|
+
cacheWrite: 0,
|
|
111
|
+
},
|
|
112
|
+
"sakana/fugu-ultra": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
|
113
|
+
"thinkingmachines/inkling": { input: 1, output: 4.05, cacheRead: 0.17, cacheWrite: 0 },
|
|
114
|
+
"thinkingmachines/inkling-small": {
|
|
115
|
+
input: 0.5,
|
|
116
|
+
output: 1.2,
|
|
117
|
+
cacheRead: 0.1,
|
|
118
|
+
cacheWrite: 0,
|
|
119
|
+
},
|
|
120
|
+
"meta/muse-spark-1.1": { input: 1.25, output: 4.25, cacheRead: 0.15, cacheWrite: 0 },
|
|
121
|
+
// Anthropic
|
|
122
|
+
// Introductory pricing through 2026-08-31.
|
|
123
|
+
"claude-sonnet-5": { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 },
|
|
124
|
+
"claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
125
|
+
"claude-fable-5": { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
|
|
126
|
+
"claude-opus-5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
127
|
+
"claude-opus-4-8": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
128
|
+
"claude-opus-4-7": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
129
|
+
"claude-haiku-4-5-20251001": {
|
|
130
|
+
input: 1,
|
|
131
|
+
output: 5,
|
|
132
|
+
cacheRead: 0.1,
|
|
133
|
+
cacheWrite: 1.25,
|
|
134
|
+
},
|
|
135
|
+
// OpenAI
|
|
136
|
+
"gpt-5.6-sol": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
137
|
+
// Discounted rates through 2026-08-14.
|
|
138
|
+
"gpt-5.6-terra": {
|
|
139
|
+
input: 1,
|
|
140
|
+
output: 6,
|
|
141
|
+
cacheRead: 0.1,
|
|
142
|
+
cacheWrite: 1.25,
|
|
143
|
+
tiers: [
|
|
144
|
+
{
|
|
145
|
+
inputTokensAbove: 272_000,
|
|
146
|
+
input: 2,
|
|
147
|
+
output: 9,
|
|
148
|
+
cacheRead: 0.2,
|
|
149
|
+
cacheWrite: 2.5,
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
"gpt-5.6-luna": {
|
|
154
|
+
input: 0.1,
|
|
155
|
+
output: 0.6,
|
|
156
|
+
cacheRead: 0.01,
|
|
157
|
+
cacheWrite: 0.125,
|
|
158
|
+
tiers: [
|
|
159
|
+
{
|
|
160
|
+
inputTokensAbove: 272_000,
|
|
161
|
+
input: 0.2,
|
|
162
|
+
output: 0.9,
|
|
163
|
+
cacheRead: 0.02,
|
|
164
|
+
cacheWrite: 0.25,
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
"gpt-5.5": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
|
169
|
+
"gpt-5.4": { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 },
|
|
170
|
+
"gpt-5.3-codex": { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 0 },
|
|
171
|
+
"gpt-5.4-mini": { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
|
|
172
|
+
// Google and xAI
|
|
173
|
+
"google/gemini-3.6-flash": { input: 1.5, output: 7.5, cacheRead: 0.15, cacheWrite: 0 },
|
|
174
|
+
"google/gemini-3.5-flash": { input: 1.5, output: 9, cacheRead: 0.15, cacheWrite: 0 },
|
|
175
|
+
"google/gemini-3.5-flash-lite": {
|
|
176
|
+
input: 0.3,
|
|
177
|
+
output: 2.5,
|
|
178
|
+
cacheRead: 0.03,
|
|
179
|
+
cacheWrite: 0,
|
|
180
|
+
},
|
|
181
|
+
"google/gemini-3.1-flash-lite": {
|
|
182
|
+
input: 0.25,
|
|
183
|
+
output: 1.5,
|
|
184
|
+
cacheRead: 0.03,
|
|
185
|
+
cacheWrite: 0,
|
|
186
|
+
},
|
|
187
|
+
"xai/grok-4.5": { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 },
|
|
188
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function projectSlugFromPath(pathName: string): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/provider/project-slug.ts — path → x-project-slug (PLAN #8, port of pi's
|
|
2
|
+
// projectSlugFromPath)
|
|
3
|
+
export function projectSlugFromPath(pathName) {
|
|
4
|
+
const slug = pathName
|
|
5
|
+
.toLowerCase()
|
|
6
|
+
.replace(/^[a-z]:/i, "")
|
|
7
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
8
|
+
.replace(/^-+|-+$/g, "");
|
|
9
|
+
return slug || "project";
|
|
10
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type PiThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
2
|
+
type CommandCodeReasoningEffort = Exclude<PiThinkingLevel, "off">;
|
|
3
|
+
/**
|
|
4
|
+
* Per-model reasoning efforts supported by Command Code's generate endpoint.
|
|
5
|
+
*
|
|
6
|
+
* The Provider API does not expose reasoning metadata. This is an exact
|
|
7
|
+
* snapshot of `reasoningEfforts` from the command-code@1.15.1 model catalog
|
|
8
|
+
* (`packages/shared/src/model-catalog.ts`, also published in the generated
|
|
9
|
+
* `dist/bundled/command-code-knowledge/reference/models.md`). Models omitted
|
|
10
|
+
* here let Command Code choose their reasoning depth, matching the CLI.
|
|
11
|
+
*/
|
|
12
|
+
export declare const MODEL_EFFORTS: Readonly<Record<string, readonly CommandCodeReasoningEffort[]>>;
|
|
13
|
+
export interface ThinkingMetadata {
|
|
14
|
+
thinkingLevelMap: Partial<Record<PiThinkingLevel, string | null>>;
|
|
15
|
+
thinking: {
|
|
16
|
+
mode: "effort";
|
|
17
|
+
effortMap: Partial<Record<CommandCodeReasoningEffort, string>>;
|
|
18
|
+
efforts: readonly CommandCodeReasoningEffort[];
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export declare function thinkingMetadataForModel(modelId: string): ThinkingMetadata | undefined;
|
|
22
|
+
export declare function isReasoningModel(modelId: string): boolean;
|
|
23
|
+
export declare function mappedReasoningEffort(model: {
|
|
24
|
+
reasoning: boolean;
|
|
25
|
+
thinkingLevelMap?: Partial<Record<PiThinkingLevel, string | null>>;
|
|
26
|
+
}, options?: {
|
|
27
|
+
reasoning?: string;
|
|
28
|
+
}): string | undefined;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/provider/reasoning.ts — reasoning-effort metadata tables (PLAN #5 Part A,
|
|
2
|
+
// port of pi's models.ts:67-150; catalog parsing deferred to #6/#7)
|
|
3
|
+
/**
|
|
4
|
+
* Per-model reasoning efforts supported by Command Code's generate endpoint.
|
|
5
|
+
*
|
|
6
|
+
* The Provider API does not expose reasoning metadata. This is an exact
|
|
7
|
+
* snapshot of `reasoningEfforts` from the command-code@1.15.1 model catalog
|
|
8
|
+
* (`packages/shared/src/model-catalog.ts`, also published in the generated
|
|
9
|
+
* `dist/bundled/command-code-knowledge/reference/models.md`). Models omitted
|
|
10
|
+
* here let Command Code choose their reasoning depth, matching the CLI.
|
|
11
|
+
*/
|
|
12
|
+
export const MODEL_EFFORTS = {
|
|
13
|
+
"Qwen/Qwen3.8-Max": ["low", "medium", "xhigh"],
|
|
14
|
+
"claude-fable-5": ["low", "medium", "high", "xhigh", "max"],
|
|
15
|
+
"claude-opus-4-7": ["low", "medium", "high", "xhigh", "max"],
|
|
16
|
+
"claude-opus-4-8": ["low", "medium", "high", "xhigh", "max"],
|
|
17
|
+
"claude-opus-5": ["low", "medium", "high", "xhigh", "max"],
|
|
18
|
+
"claude-sonnet-4-6": ["low", "medium", "high", "xhigh", "max"],
|
|
19
|
+
"claude-sonnet-5": ["low", "medium", "high", "xhigh", "max"],
|
|
20
|
+
"deepseek/deepseek-v4-flash": ["high", "max"],
|
|
21
|
+
"deepseek/deepseek-v4-pro": ["high", "max"],
|
|
22
|
+
"gpt-5.3-codex": ["low", "medium", "high", "xhigh"],
|
|
23
|
+
"gpt-5.4": ["low", "medium", "high", "xhigh"],
|
|
24
|
+
"gpt-5.4-mini": ["low", "medium", "high"],
|
|
25
|
+
"gpt-5.5": ["low", "medium", "high", "xhigh"],
|
|
26
|
+
"gpt-5.6-luna": ["low", "medium", "high", "xhigh", "max"],
|
|
27
|
+
"gpt-5.6-sol": ["low", "medium", "high", "xhigh", "max"],
|
|
28
|
+
"gpt-5.6-terra": ["low", "medium", "high", "xhigh", "max"],
|
|
29
|
+
"google/gemini-3.1-flash-lite": ["low", "medium", "high"],
|
|
30
|
+
"google/gemini-3.5-flash": ["low", "medium", "high"],
|
|
31
|
+
"google/gemini-3.5-flash-lite": ["low", "medium", "high"],
|
|
32
|
+
"google/gemini-3.6-flash": ["low", "medium", "high"],
|
|
33
|
+
"sakana/fugu-ultra": ["high", "xhigh"],
|
|
34
|
+
"xai/grok-4.5": ["low", "medium", "high"],
|
|
35
|
+
"zai-org/GLM-5.2": ["high", "max"],
|
|
36
|
+
};
|
|
37
|
+
const PI_THINKING_LEVELS = [
|
|
38
|
+
"off",
|
|
39
|
+
"minimal",
|
|
40
|
+
"low",
|
|
41
|
+
"medium",
|
|
42
|
+
"high",
|
|
43
|
+
"xhigh",
|
|
44
|
+
"max",
|
|
45
|
+
];
|
|
46
|
+
export function thinkingMetadataForModel(modelId) {
|
|
47
|
+
const efforts = MODEL_EFFORTS[modelId];
|
|
48
|
+
if (!efforts)
|
|
49
|
+
return undefined;
|
|
50
|
+
const thinkingLevelMap = {};
|
|
51
|
+
for (const level of PI_THINKING_LEVELS) {
|
|
52
|
+
if (level === "off")
|
|
53
|
+
continue;
|
|
54
|
+
thinkingLevelMap[level] = efforts.includes(level) ? level : null;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
thinkingLevelMap,
|
|
58
|
+
thinking: {
|
|
59
|
+
mode: "effort",
|
|
60
|
+
effortMap: Object.fromEntries(efforts.map((effort) => [effort, effort])),
|
|
61
|
+
efforts,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function isReasoningModel(modelId) {
|
|
66
|
+
return MODEL_EFFORTS[modelId] !== undefined;
|
|
67
|
+
}
|
|
68
|
+
export function mappedReasoningEffort(model, options) {
|
|
69
|
+
const level = options?.reasoning;
|
|
70
|
+
if (!level || level === "off" || !model.reasoning)
|
|
71
|
+
return undefined;
|
|
72
|
+
const mapped = model.thinkingLevelMap?.[level];
|
|
73
|
+
return typeof mapped === "string" && mapped !== "off" ? mapped : undefined;
|
|
74
|
+
}
|