@jeffreyjyz/opencode-command-code 0.2.11 → 0.3.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/dist/index.d.ts +1 -0
- package/dist/index.js +759 -17
- package/dist/provider/index.d.ts +22 -0
- package/dist/provider/stream.d.ts +21 -0
- package/dist/provider/wire.d.ts +22 -0
- package/dist/sidebar/windows.d.ts +6 -0
- package/dist/tui.js +10 -2
- package/dist/v2.d.ts +8 -2
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Plugin as PluginV1 } from "@opencode-ai/plugin";
|
|
2
2
|
export declare const CommandCodePlugin: PluginV1;
|
|
3
|
+
export { createCommandCode } from "./provider";
|
|
3
4
|
declare const _default: {
|
|
4
5
|
/** v1 entrypoint (opencode >=1.18.29 object entrypoints). */
|
|
5
6
|
server: PluginV1;
|
package/dist/index.js
CHANGED
|
@@ -3054,6 +3054,654 @@ async function loadModels(key) {
|
|
|
3054
3054
|
return { claude, open };
|
|
3055
3055
|
}
|
|
3056
3056
|
|
|
3057
|
+
// src/provider/stream.ts
|
|
3058
|
+
function sseDecoder(onEvent) {
|
|
3059
|
+
let buffer = "";
|
|
3060
|
+
return (text) => {
|
|
3061
|
+
buffer += text;
|
|
3062
|
+
for (;; ) {
|
|
3063
|
+
const nl = buffer.indexOf(`
|
|
3064
|
+
`);
|
|
3065
|
+
if (nl < 0)
|
|
3066
|
+
return;
|
|
3067
|
+
const line = buffer.slice(0, nl).replace(/\r$/, "");
|
|
3068
|
+
buffer = buffer.slice(nl + 1);
|
|
3069
|
+
const trimmed = line.trim();
|
|
3070
|
+
if (!trimmed || trimmed.startsWith(":") || trimmed.startsWith("event:"))
|
|
3071
|
+
continue;
|
|
3072
|
+
const payload = trimmed.startsWith("data:") ? trimmed.slice(5).trim() : trimmed;
|
|
3073
|
+
if (!payload || payload === "[DONE]")
|
|
3074
|
+
continue;
|
|
3075
|
+
try {
|
|
3076
|
+
const parsed = JSON.parse(payload);
|
|
3077
|
+
if (parsed && typeof parsed === "object")
|
|
3078
|
+
onEvent(parsed);
|
|
3079
|
+
} catch {}
|
|
3080
|
+
}
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
var text = (v) => typeof v === "string" ? v : "";
|
|
3084
|
+
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
3085
|
+
var rec = (v) => v && typeof v === "object" ? v : {};
|
|
3086
|
+
var TOOL_FINISH = new Set(["tool_use", "tool_calls", "tool-call", "function_call"]);
|
|
3087
|
+
var LENGTH_FINISH = new Set(["length", "max_tokens", "max_output_tokens", "model_context_window_exceeded"]);
|
|
3088
|
+
function finishReason(raw) {
|
|
3089
|
+
const value = (raw ?? "unknown").toLowerCase();
|
|
3090
|
+
if (TOOL_FINISH.has(value))
|
|
3091
|
+
return { unified: "tool-calls", raw };
|
|
3092
|
+
if (LENGTH_FINISH.has(value))
|
|
3093
|
+
return { unified: "length", raw };
|
|
3094
|
+
if (value === "error")
|
|
3095
|
+
return { unified: "error", raw };
|
|
3096
|
+
return { unified: "stop", raw };
|
|
3097
|
+
}
|
|
3098
|
+
function usageFrom(total, noCache, cacheRead, cacheWrite, output, reasoning) {
|
|
3099
|
+
return {
|
|
3100
|
+
inputTokens: { total, noCache, cacheRead, cacheWrite },
|
|
3101
|
+
outputTokens: { total: output, text: Math.max(0, output - reasoning), reasoning }
|
|
3102
|
+
};
|
|
3103
|
+
}
|
|
3104
|
+
function anthropicUsage(raw) {
|
|
3105
|
+
const total = num(raw.input_tokens);
|
|
3106
|
+
const cacheRead = num(raw.cache_read_input_tokens);
|
|
3107
|
+
const cacheWrite = num(raw.cache_creation_input_tokens);
|
|
3108
|
+
const output = num(raw.output_tokens);
|
|
3109
|
+
return usageFrom(total, Math.max(0, total - cacheRead - cacheWrite), cacheRead, cacheWrite, output, 0);
|
|
3110
|
+
}
|
|
3111
|
+
function openaiUsage(raw) {
|
|
3112
|
+
const total = num(raw.prompt_tokens);
|
|
3113
|
+
const cached = num(rec(raw.prompt_tokens_details).cached_tokens);
|
|
3114
|
+
const output = num(raw.completion_tokens);
|
|
3115
|
+
const reasoning = num(rec(raw.completion_tokens_details).reasoning_tokens);
|
|
3116
|
+
return usageFrom(total, Math.max(0, total - cached), cached, 0, output, reasoning);
|
|
3117
|
+
}
|
|
3118
|
+
function anthropicReducer() {
|
|
3119
|
+
const blocks = new Map;
|
|
3120
|
+
let finished = false;
|
|
3121
|
+
let warningsEmitted = false;
|
|
3122
|
+
let last = { unified: "stop", raw: "unknown" };
|
|
3123
|
+
const open = (index, kind) => {
|
|
3124
|
+
const id = `${kind === "text" ? "text" : "reasoning"}-${index}`;
|
|
3125
|
+
blocks.set(index, { type: kind, id });
|
|
3126
|
+
return [{ type: kind === "text" ? "text-start" : "reasoning-start", id }];
|
|
3127
|
+
};
|
|
3128
|
+
return {
|
|
3129
|
+
feed(event) {
|
|
3130
|
+
const type = text(event.type);
|
|
3131
|
+
if (type === "message_start") {
|
|
3132
|
+
if (warningsEmitted)
|
|
3133
|
+
return [];
|
|
3134
|
+
warningsEmitted = true;
|
|
3135
|
+
return [{ type: "stream-start", warnings: [] }];
|
|
3136
|
+
}
|
|
3137
|
+
if (type === "content_block_start") {
|
|
3138
|
+
const index = num(event.index);
|
|
3139
|
+
const block = rec(event.content_block);
|
|
3140
|
+
const blockType = text(block.type);
|
|
3141
|
+
if (blockType === "text")
|
|
3142
|
+
return open(index, "text");
|
|
3143
|
+
if (blockType === "thinking")
|
|
3144
|
+
return open(index, "thinking");
|
|
3145
|
+
if (blockType === "tool_use") {
|
|
3146
|
+
const id = text(block.id) || `tool-${index}`;
|
|
3147
|
+
blocks.set(index, { type: "tool_use", id });
|
|
3148
|
+
return [{ type: "tool-input-start", id, toolName: text(block.name) }];
|
|
3149
|
+
}
|
|
3150
|
+
blocks.set(index, { type: "other", id: `block-${index}` });
|
|
3151
|
+
return [];
|
|
3152
|
+
}
|
|
3153
|
+
if (type === "content_block_delta") {
|
|
3154
|
+
const index = num(event.index);
|
|
3155
|
+
const delta = rec(event.delta);
|
|
3156
|
+
const block = blocks.get(index);
|
|
3157
|
+
const textDelta = text(delta.text);
|
|
3158
|
+
if (textDelta) {
|
|
3159
|
+
const parts = block?.type === "text" ? [] : open(index, "text");
|
|
3160
|
+
const id = blocks.get(index)?.id ?? `text-${index}`;
|
|
3161
|
+
return [...parts, { type: "text-delta", id, delta: textDelta }];
|
|
3162
|
+
}
|
|
3163
|
+
const thinking = text(delta.thinking);
|
|
3164
|
+
if (thinking) {
|
|
3165
|
+
const parts = block?.type === "thinking" ? [] : open(index, "thinking");
|
|
3166
|
+
const id = blocks.get(index)?.id ?? `reasoning-${index}`;
|
|
3167
|
+
return [...parts, { type: "reasoning-delta", id, delta: thinking }];
|
|
3168
|
+
}
|
|
3169
|
+
const partial = text(delta.partial_json);
|
|
3170
|
+
if (partial && block?.type === "tool_use") {
|
|
3171
|
+
return [{ type: "tool-input-delta", id: block.id, delta: partial }];
|
|
3172
|
+
}
|
|
3173
|
+
return [];
|
|
3174
|
+
}
|
|
3175
|
+
if (type === "content_block_stop") {
|
|
3176
|
+
const index = num(event.index);
|
|
3177
|
+
const block = blocks.get(index);
|
|
3178
|
+
if (!block)
|
|
3179
|
+
return [];
|
|
3180
|
+
blocks.delete(index);
|
|
3181
|
+
if (block.type === "text")
|
|
3182
|
+
return [{ type: "text-end", id: block.id }];
|
|
3183
|
+
if (block.type === "thinking")
|
|
3184
|
+
return [{ type: "reasoning-end", id: block.id }];
|
|
3185
|
+
if (block.type === "tool_use")
|
|
3186
|
+
return [{ type: "tool-input-end", id: block.id }];
|
|
3187
|
+
return [];
|
|
3188
|
+
}
|
|
3189
|
+
if (type === "message_delta") {
|
|
3190
|
+
const delta = rec(event.delta);
|
|
3191
|
+
last = finishReason(text(delta.stop_reason) || undefined);
|
|
3192
|
+
if (text(delta.stop_reason) === "pause_turn")
|
|
3193
|
+
last = { unified: "stop", raw: "pause_turn" };
|
|
3194
|
+
if (finished)
|
|
3195
|
+
return [];
|
|
3196
|
+
finished = true;
|
|
3197
|
+
return [{ type: "finish", finishReason: last, usage: anthropicUsage(rec(event.usage)) }];
|
|
3198
|
+
}
|
|
3199
|
+
if (type === "message_stop" && !finished) {
|
|
3200
|
+
finished = true;
|
|
3201
|
+
return [{ type: "finish", finishReason: last, usage: usageFrom(0, 0, 0, 0, 0, 0) }];
|
|
3202
|
+
}
|
|
3203
|
+
return [];
|
|
3204
|
+
},
|
|
3205
|
+
close() {
|
|
3206
|
+
const parts = [];
|
|
3207
|
+
for (const block of blocks.values()) {
|
|
3208
|
+
if (block.type === "text")
|
|
3209
|
+
parts.push({ type: "text-end", id: block.id });
|
|
3210
|
+
if (block.type === "thinking")
|
|
3211
|
+
parts.push({ type: "reasoning-end", id: block.id });
|
|
3212
|
+
if (block.type === "tool_use")
|
|
3213
|
+
parts.push({ type: "tool-input-end", id: block.id });
|
|
3214
|
+
}
|
|
3215
|
+
blocks.clear();
|
|
3216
|
+
return parts;
|
|
3217
|
+
}
|
|
3218
|
+
};
|
|
3219
|
+
}
|
|
3220
|
+
function openaiReducer() {
|
|
3221
|
+
const tools = new Map;
|
|
3222
|
+
let textId;
|
|
3223
|
+
let reasoningId;
|
|
3224
|
+
let pending;
|
|
3225
|
+
let usage;
|
|
3226
|
+
let finished = false;
|
|
3227
|
+
const closeText = () => {
|
|
3228
|
+
if (!textId)
|
|
3229
|
+
return [];
|
|
3230
|
+
const id = textId;
|
|
3231
|
+
textId = undefined;
|
|
3232
|
+
return [{ type: "text-end", id }];
|
|
3233
|
+
};
|
|
3234
|
+
const closeReasoning = () => {
|
|
3235
|
+
if (!reasoningId)
|
|
3236
|
+
return [];
|
|
3237
|
+
const id = reasoningId;
|
|
3238
|
+
reasoningId = undefined;
|
|
3239
|
+
return [{ type: "reasoning-end", id }];
|
|
3240
|
+
};
|
|
3241
|
+
const completeIfParsed = (buffer) => {
|
|
3242
|
+
if (!buffer.opened || buffer.closed)
|
|
3243
|
+
return [];
|
|
3244
|
+
try {
|
|
3245
|
+
JSON.parse(buffer.input);
|
|
3246
|
+
} catch {
|
|
3247
|
+
return [];
|
|
3248
|
+
}
|
|
3249
|
+
buffer.closed = true;
|
|
3250
|
+
return [
|
|
3251
|
+
{ type: "tool-input-end", id: buffer.id },
|
|
3252
|
+
{ type: "tool-call", toolCallId: buffer.id, toolName: buffer.name, input: buffer.input }
|
|
3253
|
+
];
|
|
3254
|
+
};
|
|
3255
|
+
const flushTools = () => {
|
|
3256
|
+
const parts = [];
|
|
3257
|
+
for (const [index, buffer] of tools) {
|
|
3258
|
+
if (buffer.opened && !buffer.closed) {
|
|
3259
|
+
parts.push({ type: "tool-input-end", id: buffer.id });
|
|
3260
|
+
parts.push({ type: "tool-call", toolCallId: buffer.id, toolName: buffer.name, input: buffer.input });
|
|
3261
|
+
buffer.closed = true;
|
|
3262
|
+
}
|
|
3263
|
+
tools.delete(index);
|
|
3264
|
+
}
|
|
3265
|
+
return parts;
|
|
3266
|
+
};
|
|
3267
|
+
return {
|
|
3268
|
+
feed(event) {
|
|
3269
|
+
if (text(event.type) === "error" || event.error !== undefined) {
|
|
3270
|
+
const message = text(rec(event.error).message) || text(event.message) || "provider stream error";
|
|
3271
|
+
return [{ type: "error", error: new Error(message) }];
|
|
3272
|
+
}
|
|
3273
|
+
const parts = [];
|
|
3274
|
+
const choices = Array.isArray(event.choices) ? event.choices : [];
|
|
3275
|
+
const choice = rec(choices[0]);
|
|
3276
|
+
const delta = rec(choice.delta);
|
|
3277
|
+
const reasoning = text(delta.reasoning) || text(delta.reasoning_content);
|
|
3278
|
+
if (reasoning) {
|
|
3279
|
+
if (!reasoningId) {
|
|
3280
|
+
parts.push(...closeText());
|
|
3281
|
+
reasoningId = "reasoning-0";
|
|
3282
|
+
parts.push({ type: "reasoning-start", id: reasoningId });
|
|
3283
|
+
}
|
|
3284
|
+
parts.push({ type: "reasoning-delta", id: reasoningId, delta: reasoning });
|
|
3285
|
+
}
|
|
3286
|
+
const content = text(delta.content);
|
|
3287
|
+
if (content) {
|
|
3288
|
+
if (reasoningId)
|
|
3289
|
+
parts.push(...closeReasoning());
|
|
3290
|
+
if (!textId) {
|
|
3291
|
+
textId = "text-0";
|
|
3292
|
+
parts.push({ type: "text-start", id: textId });
|
|
3293
|
+
}
|
|
3294
|
+
parts.push({ type: "text-delta", id: textId, delta: content });
|
|
3295
|
+
}
|
|
3296
|
+
const calls = Array.isArray(delta.tool_calls) ? delta.tool_calls : [];
|
|
3297
|
+
if (calls.length > 0) {
|
|
3298
|
+
parts.push(...closeReasoning(), ...closeText());
|
|
3299
|
+
for (const raw of calls) {
|
|
3300
|
+
const call = rec(raw);
|
|
3301
|
+
const fn = rec(call.function);
|
|
3302
|
+
const index = typeof call.index === "number" ? call.index : tools.size;
|
|
3303
|
+
let buffer = tools.get(index);
|
|
3304
|
+
if (!buffer) {
|
|
3305
|
+
buffer = { id: text(call.id) || `tool-${index}`, name: text(fn.name), input: "", opened: false, closed: false };
|
|
3306
|
+
tools.set(index, buffer);
|
|
3307
|
+
}
|
|
3308
|
+
if (text(call.id))
|
|
3309
|
+
buffer.id = text(call.id);
|
|
3310
|
+
if (text(fn.name))
|
|
3311
|
+
buffer.name = text(fn.name);
|
|
3312
|
+
const args = text(fn.arguments);
|
|
3313
|
+
if (!buffer.opened && buffer.name) {
|
|
3314
|
+
buffer.opened = true;
|
|
3315
|
+
parts.push({ type: "tool-input-start", id: buffer.id, toolName: buffer.name });
|
|
3316
|
+
if (buffer.input)
|
|
3317
|
+
parts.push({ type: "tool-input-delta", id: buffer.id, delta: buffer.input });
|
|
3318
|
+
} else if (args && !buffer.closed) {
|
|
3319
|
+
parts.push({ type: "tool-input-delta", id: buffer.id, delta: args });
|
|
3320
|
+
}
|
|
3321
|
+
if (!buffer.closed) {
|
|
3322
|
+
buffer.input += args;
|
|
3323
|
+
parts.push(...completeIfParsed(buffer));
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
const rawFinish = text(choice.finish_reason) || text(choice.finishReason) || text(event.finish_reason) || text(event.finishReason);
|
|
3328
|
+
if (rawFinish)
|
|
3329
|
+
pending = finishReason(rawFinish);
|
|
3330
|
+
if (event.usage !== undefined && event.usage !== null)
|
|
3331
|
+
usage = openaiUsage(rec(event.usage));
|
|
3332
|
+
if (pending && !finished && (usage !== undefined || choices.length === 0)) {
|
|
3333
|
+
finished = true;
|
|
3334
|
+
parts.push(...closeReasoning(), ...closeText(), ...flushTools());
|
|
3335
|
+
parts.push({ type: "finish", finishReason: pending, usage: usage ?? usageFrom(0, 0, 0, 0, 0, 0) });
|
|
3336
|
+
}
|
|
3337
|
+
return parts;
|
|
3338
|
+
},
|
|
3339
|
+
close() {
|
|
3340
|
+
const parts = [...closeReasoning(), ...closeText(), ...flushTools()];
|
|
3341
|
+
if (pending && !finished) {
|
|
3342
|
+
finished = true;
|
|
3343
|
+
parts.push({ type: "finish", finishReason: pending, usage: usage ?? usageFrom(0, 0, 0, 0, 0, 0) });
|
|
3344
|
+
}
|
|
3345
|
+
return parts;
|
|
3346
|
+
}
|
|
3347
|
+
};
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
// src/provider/wire.ts
|
|
3351
|
+
var DEFAULT_MAX_TOKENS = 64000;
|
|
3352
|
+
function capTokens(value) {
|
|
3353
|
+
const n = typeof value === "number" && Number.isFinite(value) ? value : DEFAULT_MAX_TOKENS;
|
|
3354
|
+
return Math.min(n, DEFAULT_MAX_TOKENS);
|
|
3355
|
+
}
|
|
3356
|
+
function textOf(part) {
|
|
3357
|
+
const p = part;
|
|
3358
|
+
return typeof p.text === "string" ? p.text : "";
|
|
3359
|
+
}
|
|
3360
|
+
function resultText(part) {
|
|
3361
|
+
const output = part.output ?? part.result;
|
|
3362
|
+
if (typeof output === "string")
|
|
3363
|
+
return output;
|
|
3364
|
+
if (output && typeof output === "object") {
|
|
3365
|
+
const o = output;
|
|
3366
|
+
if (typeof o.value === "string")
|
|
3367
|
+
return o.value;
|
|
3368
|
+
if (o.value !== undefined)
|
|
3369
|
+
return JSON.stringify(o.value);
|
|
3370
|
+
if (typeof o.reason === "string")
|
|
3371
|
+
return o.reason;
|
|
3372
|
+
}
|
|
3373
|
+
return JSON.stringify(output ?? "");
|
|
3374
|
+
}
|
|
3375
|
+
function imageUrl(part) {
|
|
3376
|
+
const p = part;
|
|
3377
|
+
const mime = p.mediaType ?? p.mimeType ?? "application/octet-stream";
|
|
3378
|
+
const data = p.data;
|
|
3379
|
+
if (typeof data === "string")
|
|
3380
|
+
return data.startsWith("data:") ? data : `data:${mime};base64,${data}`;
|
|
3381
|
+
if (data instanceof Uint8Array)
|
|
3382
|
+
return `data:${mime};base64,${Buffer.from(data).toString("base64")}`;
|
|
3383
|
+
if (data instanceof URL)
|
|
3384
|
+
return data.href;
|
|
3385
|
+
return;
|
|
3386
|
+
}
|
|
3387
|
+
function pairedToolCallIds(prompt) {
|
|
3388
|
+
const calls = new Set;
|
|
3389
|
+
const results = new Set;
|
|
3390
|
+
for (const message of prompt) {
|
|
3391
|
+
for (const part of message.content) {
|
|
3392
|
+
if (part.type === "tool-call" && typeof part.toolCallId === "string")
|
|
3393
|
+
calls.add(part.toolCallId);
|
|
3394
|
+
if (part.type === "tool-result" && typeof part.toolCallId === "string")
|
|
3395
|
+
results.add(part.toolCallId);
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
return new Set([...calls].filter((id) => results.has(id)));
|
|
3399
|
+
}
|
|
3400
|
+
function systemText(prompt) {
|
|
3401
|
+
return prompt.filter((m) => m.role === "system").map((m) => m.content).join(`
|
|
3402
|
+
`);
|
|
3403
|
+
}
|
|
3404
|
+
function anthropicUserContent(content, images) {
|
|
3405
|
+
if (typeof content === "string")
|
|
3406
|
+
return content;
|
|
3407
|
+
const parts = content ?? [];
|
|
3408
|
+
const blocks = [];
|
|
3409
|
+
for (const part of parts) {
|
|
3410
|
+
if (part.type === "text")
|
|
3411
|
+
blocks.push({ type: "text", text: textOf(part) });
|
|
3412
|
+
else if (part.type === "file" && images) {
|
|
3413
|
+
const url = imageUrl(part);
|
|
3414
|
+
if (url?.startsWith("data:")) {
|
|
3415
|
+
const [head, b64] = url.slice(5).split(";base64,");
|
|
3416
|
+
blocks.push({ type: "image", source: { type: "base64", media_type: head, data: b64 } });
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
if (blocks.length === 1 && blocks[0].type === "text") {
|
|
3421
|
+
return blocks[0].text;
|
|
3422
|
+
}
|
|
3423
|
+
return blocks;
|
|
3424
|
+
}
|
|
3425
|
+
function openaiUserContent(content, images) {
|
|
3426
|
+
if (typeof content === "string")
|
|
3427
|
+
return content;
|
|
3428
|
+
const parts = content ?? [];
|
|
3429
|
+
const blocks = [];
|
|
3430
|
+
for (const part of parts) {
|
|
3431
|
+
if (part.type === "text")
|
|
3432
|
+
blocks.push({ type: "text", text: textOf(part) });
|
|
3433
|
+
else if (part.type === "file" && images) {
|
|
3434
|
+
const url = imageUrl(part);
|
|
3435
|
+
if (url)
|
|
3436
|
+
blocks.push({ type: "image_url", image_url: { url } });
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
if (blocks.every((b) => b.type === "text")) {
|
|
3440
|
+
return blocks.map((b) => b.text).join(`
|
|
3441
|
+
`);
|
|
3442
|
+
}
|
|
3443
|
+
return blocks;
|
|
3444
|
+
}
|
|
3445
|
+
function anthropicBody(model, prompt, options) {
|
|
3446
|
+
const paired = pairedToolCallIds(prompt);
|
|
3447
|
+
const messages = [];
|
|
3448
|
+
for (const message of prompt) {
|
|
3449
|
+
if (message.role === "system")
|
|
3450
|
+
continue;
|
|
3451
|
+
if (message.role === "user") {
|
|
3452
|
+
messages.push({ role: "user", content: anthropicUserContent(message.content, options.images) });
|
|
3453
|
+
continue;
|
|
3454
|
+
}
|
|
3455
|
+
if (message.role === "assistant") {
|
|
3456
|
+
const content = [];
|
|
3457
|
+
for (const part of message.content) {
|
|
3458
|
+
if (part.type === "text") {
|
|
3459
|
+
const text = textOf(part);
|
|
3460
|
+
if (text)
|
|
3461
|
+
content.push({ type: "text", text });
|
|
3462
|
+
} else if (part.type === "tool-call" && paired.has(String(part.toolCallId))) {
|
|
3463
|
+
content.push({ type: "tool_use", id: part.toolCallId, name: part.toolName, input: part.input ?? {} });
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
if (content.length > 0)
|
|
3467
|
+
messages.push({ role: "assistant", content });
|
|
3468
|
+
continue;
|
|
3469
|
+
}
|
|
3470
|
+
for (const part of message.content) {
|
|
3471
|
+
if (part.type !== "tool-result" || !paired.has(String(part.toolCallId)))
|
|
3472
|
+
continue;
|
|
3473
|
+
messages.push({
|
|
3474
|
+
role: "user",
|
|
3475
|
+
content: [
|
|
3476
|
+
{
|
|
3477
|
+
type: "tool_result",
|
|
3478
|
+
tool_use_id: part.toolCallId,
|
|
3479
|
+
content: resultText(part)
|
|
3480
|
+
}
|
|
3481
|
+
]
|
|
3482
|
+
});
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
const system = systemText(prompt);
|
|
3486
|
+
const body = { model, stream: true, messages, max_tokens: capTokens(options.maxOutputTokens) };
|
|
3487
|
+
if (system)
|
|
3488
|
+
body.system = [{ type: "text", text: system, cache_control: { type: "ephemeral" } }];
|
|
3489
|
+
if (options.tools?.length) {
|
|
3490
|
+
body.tools = options.tools.map((t) => ({ name: t.name, description: t.description, input_schema: t.inputSchema ?? {} }));
|
|
3491
|
+
}
|
|
3492
|
+
if (options.temperature !== undefined)
|
|
3493
|
+
body.temperature = options.temperature;
|
|
3494
|
+
if (options.reasoningEffort)
|
|
3495
|
+
body.reasoning_effort = options.reasoningEffort;
|
|
3496
|
+
return body;
|
|
3497
|
+
}
|
|
3498
|
+
function openaiBody(model, prompt, options) {
|
|
3499
|
+
const paired = pairedToolCallIds(prompt);
|
|
3500
|
+
const messages = [];
|
|
3501
|
+
const system = systemText(prompt);
|
|
3502
|
+
if (system)
|
|
3503
|
+
messages.push({ role: "system", content: system });
|
|
3504
|
+
for (const message of prompt) {
|
|
3505
|
+
if (message.role === "system")
|
|
3506
|
+
continue;
|
|
3507
|
+
if (message.role === "user") {
|
|
3508
|
+
messages.push({ role: "user", content: openaiUserContent(message.content, options.images) });
|
|
3509
|
+
continue;
|
|
3510
|
+
}
|
|
3511
|
+
if (message.role === "assistant") {
|
|
3512
|
+
const calls = message.content.filter((p) => p.type === "tool-call" && paired.has(String(p.toolCallId)));
|
|
3513
|
+
const texts = message.content.filter((p) => p.type === "text").map(textOf).filter(Boolean);
|
|
3514
|
+
const reasoning = message.content.filter((p) => p.type === "reasoning").map(textOf).join("");
|
|
3515
|
+
if (calls.length > 0) {
|
|
3516
|
+
messages.push({
|
|
3517
|
+
role: "assistant",
|
|
3518
|
+
...reasoning ? { reasoning_content: reasoning } : {},
|
|
3519
|
+
content: texts.length ? texts.join(`
|
|
3520
|
+
`) : null,
|
|
3521
|
+
tool_calls: calls.map((c) => ({
|
|
3522
|
+
id: c.toolCallId,
|
|
3523
|
+
type: "function",
|
|
3524
|
+
function: { name: c.toolName, arguments: JSON.stringify(c.input ?? {}) }
|
|
3525
|
+
}))
|
|
3526
|
+
});
|
|
3527
|
+
} else if (texts.length > 0) {
|
|
3528
|
+
messages.push({ role: "assistant", ...reasoning ? { reasoning_content: reasoning } : {}, content: texts.join(`
|
|
3529
|
+
`) });
|
|
3530
|
+
}
|
|
3531
|
+
continue;
|
|
3532
|
+
}
|
|
3533
|
+
for (const part of message.content) {
|
|
3534
|
+
if (part.type !== "tool-result" || !paired.has(String(part.toolCallId)))
|
|
3535
|
+
continue;
|
|
3536
|
+
messages.push({
|
|
3537
|
+
role: "tool",
|
|
3538
|
+
tool_call_id: part.toolCallId,
|
|
3539
|
+
content: resultText(part)
|
|
3540
|
+
});
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
const body = { model, stream: true, messages, max_tokens: capTokens(options.maxOutputTokens) };
|
|
3544
|
+
if (options.tools?.length) {
|
|
3545
|
+
body.tools = options.tools.map((t) => ({
|
|
3546
|
+
type: "function",
|
|
3547
|
+
function: { name: t.name, description: t.description, parameters: t.inputSchema ?? {} }
|
|
3548
|
+
}));
|
|
3549
|
+
}
|
|
3550
|
+
if (options.temperature !== undefined)
|
|
3551
|
+
body.temperature = options.temperature;
|
|
3552
|
+
if (options.reasoningEffort)
|
|
3553
|
+
body.reasoning_effort = options.reasoningEffort;
|
|
3554
|
+
body.stream_options = { include_usage: true };
|
|
3555
|
+
return body;
|
|
3556
|
+
}
|
|
3557
|
+
function requestBody(lane, model, prompt, options) {
|
|
3558
|
+
return lane === "anthropic" ? anthropicBody(model, prompt, options) : openaiBody(model, prompt, options);
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
// src/provider/index.ts
|
|
3562
|
+
var DEFAULT_BASE_URL = "https://api.commandcode.ai";
|
|
3563
|
+
function laneOf(modelId) {
|
|
3564
|
+
const bare = modelId.includes("/") ? modelId.slice(modelId.lastIndexOf("/") + 1) : modelId;
|
|
3565
|
+
return bare.toLowerCase().startsWith("claude") ? "anthropic" : "openai";
|
|
3566
|
+
}
|
|
3567
|
+
function endpoint(lane) {
|
|
3568
|
+
return lane === "anthropic" ? "/provider/v1/messages" : "/provider/v1/chat/completions";
|
|
3569
|
+
}
|
|
3570
|
+
function effortFrom(options) {
|
|
3571
|
+
const providerOptions = options.providerOptions ?? {};
|
|
3572
|
+
const top = providerOptions.reasoning ?? providerOptions.reasoningEffort;
|
|
3573
|
+
if (typeof top === "string")
|
|
3574
|
+
return top;
|
|
3575
|
+
const namespaced = providerOptions.commandcode;
|
|
3576
|
+
if (namespaced && typeof namespaced === "object") {
|
|
3577
|
+
const value = namespaced.reasoning ?? namespaced.reasoningEffort;
|
|
3578
|
+
if (typeof value === "string")
|
|
3579
|
+
return value;
|
|
3580
|
+
}
|
|
3581
|
+
return;
|
|
3582
|
+
}
|
|
3583
|
+
function toolsOf(options) {
|
|
3584
|
+
const tools = options.tools ?? [];
|
|
3585
|
+
if (tools.length === 0)
|
|
3586
|
+
return;
|
|
3587
|
+
return tools.map((tool) => ({
|
|
3588
|
+
name: tool.name,
|
|
3589
|
+
description: "description" in tool ? tool.description : undefined,
|
|
3590
|
+
inputSchema: tool.inputSchema ?? {}
|
|
3591
|
+
}));
|
|
3592
|
+
}
|
|
3593
|
+
function createCommandCode(options = {}, images = () => false) {
|
|
3594
|
+
const baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
3595
|
+
const doFetch = options.fetch ?? fetch;
|
|
3596
|
+
function model(modelId) {
|
|
3597
|
+
const lane = laneOf(modelId);
|
|
3598
|
+
return {
|
|
3599
|
+
specificationVersion: "v3",
|
|
3600
|
+
provider: "command-code",
|
|
3601
|
+
modelId,
|
|
3602
|
+
supportedUrls: {},
|
|
3603
|
+
async doStream(callOptions) {
|
|
3604
|
+
const body = requestBody(lane, modelId, callOptions.prompt, {
|
|
3605
|
+
maxOutputTokens: callOptions.maxOutputTokens,
|
|
3606
|
+
temperature: callOptions.temperature,
|
|
3607
|
+
tools: toolsOf(callOptions),
|
|
3608
|
+
reasoningEffort: effortFrom(callOptions) ?? options.reasoningEffort,
|
|
3609
|
+
images: images(modelId)
|
|
3610
|
+
});
|
|
3611
|
+
const response = await doFetch(`${baseURL}${endpoint(lane)}`, {
|
|
3612
|
+
method: "POST",
|
|
3613
|
+
headers: {
|
|
3614
|
+
"Content-Type": "application/json",
|
|
3615
|
+
Authorization: `Bearer ${options.apiKey ?? ""}`,
|
|
3616
|
+
...options.headers ?? {},
|
|
3617
|
+
...callOptions.headers ?? {}
|
|
3618
|
+
},
|
|
3619
|
+
body: JSON.stringify(body),
|
|
3620
|
+
signal: callOptions.abortSignal
|
|
3621
|
+
});
|
|
3622
|
+
if (!response.ok || !response.body) {
|
|
3623
|
+
const detail = await response.text().catch(() => "");
|
|
3624
|
+
throw new Error(`command-code ${lane} ${response.status}: ${detail.slice(0, 300)}`);
|
|
3625
|
+
}
|
|
3626
|
+
const reducer = lane === "anthropic" ? anthropicReducer() : openaiReducer();
|
|
3627
|
+
const stream = new ReadableStream({
|
|
3628
|
+
async start(controller) {
|
|
3629
|
+
const reader = response.body.getReader();
|
|
3630
|
+
const decoder = new TextDecoder;
|
|
3631
|
+
const emit = (parts) => {
|
|
3632
|
+
for (const part of parts)
|
|
3633
|
+
controller.enqueue(part);
|
|
3634
|
+
};
|
|
3635
|
+
const decode = sseDecoder((event) => emit(reducer.feed(event)));
|
|
3636
|
+
try {
|
|
3637
|
+
for (;; ) {
|
|
3638
|
+
const { done, value } = await reader.read();
|
|
3639
|
+
if (done)
|
|
3640
|
+
break;
|
|
3641
|
+
decode(decoder.decode(value, { stream: true }));
|
|
3642
|
+
}
|
|
3643
|
+
emit(reducer.close());
|
|
3644
|
+
controller.close();
|
|
3645
|
+
} catch (error) {
|
|
3646
|
+
controller.enqueue({ type: "error", error });
|
|
3647
|
+
controller.close();
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
});
|
|
3651
|
+
return { stream, response: { headers: Object.fromEntries(response.headers) } };
|
|
3652
|
+
},
|
|
3653
|
+
async doGenerate(callOptions) {
|
|
3654
|
+
const { stream } = await this.doStream(callOptions);
|
|
3655
|
+
const reader = stream.getReader();
|
|
3656
|
+
const content = [];
|
|
3657
|
+
let finish = { unified: "stop", raw: "unknown" };
|
|
3658
|
+
let usage = {
|
|
3659
|
+
inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
|
|
3660
|
+
outputTokens: { total: 0, text: 0, reasoning: 0 }
|
|
3661
|
+
};
|
|
3662
|
+
const textOf = {};
|
|
3663
|
+
const toolsById = new Map;
|
|
3664
|
+
for (;; ) {
|
|
3665
|
+
const { done, value } = await reader.read();
|
|
3666
|
+
if (done)
|
|
3667
|
+
break;
|
|
3668
|
+
const part = value;
|
|
3669
|
+
if (part.type === "text-delta")
|
|
3670
|
+
textOf[String(part.id)] = (textOf[String(part.id)] ?? "") + String(part.delta);
|
|
3671
|
+
else if (part.type === "reasoning-delta") {
|
|
3672
|
+
content.push({ type: "reasoning", text: String(part.delta) });
|
|
3673
|
+
} else if (part.type === "tool-input-start") {
|
|
3674
|
+
toolsById.set(String(part.id), { name: String(part.toolName), input: "" });
|
|
3675
|
+
} else if (part.type === "tool-input-delta") {
|
|
3676
|
+
const buffer = toolsById.get(String(part.id));
|
|
3677
|
+
if (buffer)
|
|
3678
|
+
buffer.input += String(part.delta);
|
|
3679
|
+
} else if (part.type === "tool-call") {
|
|
3680
|
+
content.push({
|
|
3681
|
+
type: "tool-call",
|
|
3682
|
+
toolCallId: part.toolCallId,
|
|
3683
|
+
toolName: part.toolName,
|
|
3684
|
+
input: part.input
|
|
3685
|
+
});
|
|
3686
|
+
} else if (part.type === "finish") {
|
|
3687
|
+
finish = part;
|
|
3688
|
+
usage = part.usage;
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
for (const [id, value] of Object.entries(textOf))
|
|
3692
|
+
content.push({ type: "text", text: value, id });
|
|
3693
|
+
return {
|
|
3694
|
+
content,
|
|
3695
|
+
finishReason: finish.finishReason,
|
|
3696
|
+
usage,
|
|
3697
|
+
warnings: []
|
|
3698
|
+
};
|
|
3699
|
+
}
|
|
3700
|
+
};
|
|
3701
|
+
}
|
|
3702
|
+
return { languageModel: model };
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3057
3705
|
// src/v2.ts
|
|
3058
3706
|
var PLUGIN_ID = "command-code";
|
|
3059
3707
|
var PROVIDER_BASE = "https://api.commandcode.ai/provider/v1";
|
|
@@ -3103,6 +3751,19 @@ function staticSeedModels() {
|
|
|
3103
3751
|
}
|
|
3104
3752
|
return seed;
|
|
3105
3753
|
}
|
|
3754
|
+
function mergeModels(snapshot, live) {
|
|
3755
|
+
const byId = new Map;
|
|
3756
|
+
const add = (model) => {
|
|
3757
|
+
const id = model?.id;
|
|
3758
|
+
if (typeof id === "string")
|
|
3759
|
+
byId.set(id, model);
|
|
3760
|
+
};
|
|
3761
|
+
for (const model of snapshot)
|
|
3762
|
+
add(model);
|
|
3763
|
+
for (const model of live)
|
|
3764
|
+
add(model);
|
|
3765
|
+
return [...byId.values()];
|
|
3766
|
+
}
|
|
3106
3767
|
async function credentialKey(ctx) {
|
|
3107
3768
|
try {
|
|
3108
3769
|
const connection = await ctx.integration.connection.active(INTEGRATION_ID);
|
|
@@ -3123,17 +3784,14 @@ async function credentialKey(ctx) {
|
|
|
3123
3784
|
var commandCodeV2 = {
|
|
3124
3785
|
id: PLUGIN_ID,
|
|
3125
3786
|
async setup(ctx) {
|
|
3787
|
+
const t0 = Date.now();
|
|
3126
3788
|
let localKey;
|
|
3789
|
+
let keyMs = 0;
|
|
3127
3790
|
try {
|
|
3791
|
+
const keyStart = Date.now();
|
|
3128
3792
|
localKey = await resolveKey();
|
|
3793
|
+
keyMs = Date.now() - keyStart;
|
|
3129
3794
|
} catch {}
|
|
3130
|
-
let hasConnection = false;
|
|
3131
|
-
try {
|
|
3132
|
-
hasConnection = await ctx.integration.connection.active(INTEGRATION_ID) !== undefined;
|
|
3133
|
-
} catch {}
|
|
3134
|
-
if (!localKey && !hasConnection) {
|
|
3135
|
-
console.warn(`[command-code] no API key found. Run /connect and choose "${INTEGRATION_NAME}", or \`cmd login\` (~/.commandcode/auth.json), or set CMD_API_KEY.`);
|
|
3136
|
-
}
|
|
3137
3795
|
await ctx.integration.transform((editor) => {
|
|
3138
3796
|
editor.update(INTEGRATION_ID, (integration) => {
|
|
3139
3797
|
if (integration.name === integration.id) {
|
|
@@ -3149,6 +3807,17 @@ var commandCodeV2 = {
|
|
|
3149
3807
|
method: { type: "key", label: "Command Code API key" }
|
|
3150
3808
|
});
|
|
3151
3809
|
});
|
|
3810
|
+
const images = (modelId) => inputModalities(modelId).includes("image");
|
|
3811
|
+
for (const lane of LANES) {
|
|
3812
|
+
await ctx.aisdk.hook("sdk", (event) => {
|
|
3813
|
+
const options = event.options ?? {};
|
|
3814
|
+
event.sdk = createCommandCode({
|
|
3815
|
+
apiKey: typeof options.apiKey === "string" ? options.apiKey : undefined,
|
|
3816
|
+
baseURL: typeof options.baseURL === "string" ? options.baseURL : PROVIDER_BASE,
|
|
3817
|
+
headers: options.headers
|
|
3818
|
+
}, images);
|
|
3819
|
+
}, { providerID: lane.id });
|
|
3820
|
+
}
|
|
3152
3821
|
const seed = staticSeedModels();
|
|
3153
3822
|
await ctx.provider.transform((editor) => {
|
|
3154
3823
|
for (const lane of LANES) {
|
|
@@ -3157,9 +3826,9 @@ var commandCodeV2 = {
|
|
|
3157
3826
|
provider.name = lane.name;
|
|
3158
3827
|
if (provider.package === "")
|
|
3159
3828
|
provider.package = lane.pkg;
|
|
3160
|
-
provider.activation =
|
|
3829
|
+
provider.activation = localKey ? "enabled" : "auto";
|
|
3161
3830
|
provider.settings = { ...provider.settings, baseURL: PROVIDER_BASE };
|
|
3162
|
-
if (
|
|
3831
|
+
if (localKey && provider.settings.apiKey === undefined) {
|
|
3163
3832
|
provider.settings.apiKey = localKey;
|
|
3164
3833
|
}
|
|
3165
3834
|
provider.integrationID ??= INTEGRATION_ID;
|
|
@@ -3167,6 +3836,7 @@ var commandCodeV2 = {
|
|
|
3167
3836
|
editor.models.set(lane.id, seed[lane.id]);
|
|
3168
3837
|
}
|
|
3169
3838
|
});
|
|
3839
|
+
const registerMs = Date.now() - t0;
|
|
3170
3840
|
await ctx.tool.transform((editor) => {
|
|
3171
3841
|
editor.add({
|
|
3172
3842
|
name: "cmd_usage",
|
|
@@ -3190,12 +3860,32 @@ var commandCodeV2 = {
|
|
|
3190
3860
|
}
|
|
3191
3861
|
});
|
|
3192
3862
|
});
|
|
3863
|
+
let hasConnection = false;
|
|
3864
|
+
let connectionMs = 0;
|
|
3865
|
+
try {
|
|
3866
|
+
const connectionStart = Date.now();
|
|
3867
|
+
hasConnection = await ctx.integration.connection.active(INTEGRATION_ID) !== undefined;
|
|
3868
|
+
connectionMs = Date.now() - connectionStart;
|
|
3869
|
+
} catch {}
|
|
3870
|
+
if (hasConnection) {
|
|
3871
|
+
await ctx.provider.transform((editor) => {
|
|
3872
|
+
for (const lane of LANES) {
|
|
3873
|
+
editor.update(lane.id, (provider) => {
|
|
3874
|
+
provider.activation = "enabled";
|
|
3875
|
+
});
|
|
3876
|
+
}
|
|
3877
|
+
});
|
|
3878
|
+
}
|
|
3879
|
+
if (!localKey && !hasConnection) {
|
|
3880
|
+
console.warn(`[command-code] no API key found. Run /connect and choose "${INTEGRATION_NAME}", or \`cmd login\` (~/.commandcode/auth.json), or set CMD_API_KEY.`);
|
|
3881
|
+
}
|
|
3193
3882
|
const controller = new AbortController;
|
|
3194
3883
|
let refreshing = false;
|
|
3195
3884
|
const refresh = async () => {
|
|
3196
3885
|
if (refreshing || controller.signal.aborted)
|
|
3197
3886
|
return;
|
|
3198
3887
|
refreshing = true;
|
|
3888
|
+
const refreshStart = Date.now();
|
|
3199
3889
|
try {
|
|
3200
3890
|
const key = await credentialKey(ctx);
|
|
3201
3891
|
if (!key)
|
|
@@ -3204,11 +3894,12 @@ var commandCodeV2 = {
|
|
|
3204
3894
|
if (controller.signal.aborted)
|
|
3205
3895
|
return;
|
|
3206
3896
|
await ctx.provider.transform((editor) => {
|
|
3207
|
-
editor.models.set("command-code-anthropic", split.claude.map((m) => toV2Model(m, LANES[0])));
|
|
3208
|
-
editor.models.set("command-code-openai", split.open.map((m) => toV2Model(m, LANES[1])));
|
|
3897
|
+
editor.models.set("command-code-anthropic", mergeModels(seed["command-code-anthropic"], split.claude.map((m) => toV2Model(m, LANES[0]))));
|
|
3898
|
+
editor.models.set("command-code-openai", mergeModels(seed["command-code-openai"], split.open.map((m) => toV2Model(m, LANES[1]))));
|
|
3209
3899
|
});
|
|
3900
|
+
console.log(`[command-code] setup: key=${keyMs}ms register=${registerMs}ms connection=${connectionMs}ms refresh=${Date.now() - refreshStart}ms models=${split.claude.length + split.open.length}`);
|
|
3210
3901
|
} catch (e) {
|
|
3211
|
-
console.warn("[command-code] live model list unavailable, keeping
|
|
3902
|
+
console.warn("[command-code] live model list unavailable, keeping the snapshot:", e);
|
|
3212
3903
|
} finally {
|
|
3213
3904
|
refreshing = false;
|
|
3214
3905
|
}
|
|
@@ -3221,8 +3912,58 @@ var commandCodeV2 = {
|
|
|
3221
3912
|
};
|
|
3222
3913
|
}
|
|
3223
3914
|
};
|
|
3915
|
+
// package.json
|
|
3916
|
+
var package_default = {
|
|
3917
|
+
name: "@jeffreyjyz/opencode-command-code",
|
|
3918
|
+
version: "0.3.1",
|
|
3919
|
+
description: "Command Code provider + usage dashboard for opencode: two providers (Anthropic wire for Claude, OpenAI wire for everything else) against api.commandcode.ai, live model list, plan gating, /cmd-usage. Requires the cmduse CLI for usage rendering.",
|
|
3920
|
+
type: "module",
|
|
3921
|
+
main: "dist/index.js",
|
|
3922
|
+
types: "dist/index.d.ts",
|
|
3923
|
+
exports: {
|
|
3924
|
+
".": "./dist/index.js",
|
|
3925
|
+
"./tui": "./dist/tui.js"
|
|
3926
|
+
},
|
|
3927
|
+
scripts: {
|
|
3928
|
+
build: "rm -rf dist && bun build src/index.ts --outdir dist --target bun --external @opencode-ai/plugin --external @opencode/plugin --external @opencode/plugin/tui && bun scripts/build-tui.ts && tsc -p tsconfig.build.json",
|
|
3929
|
+
test: "bun test",
|
|
3930
|
+
typecheck: "tsc --noEmit",
|
|
3931
|
+
"build:tui": "bun scripts/build-tui.ts",
|
|
3932
|
+
"extract:catalog": "bun scripts/extract-catalog.ts"
|
|
3933
|
+
},
|
|
3934
|
+
dependencies: {
|
|
3935
|
+
"@opentui/core": "^0.5.4",
|
|
3936
|
+
"@opentui/solid": "^0.5.4",
|
|
3937
|
+
"solid-js": "1.9.12",
|
|
3938
|
+
zod: "4.1.8"
|
|
3939
|
+
},
|
|
3940
|
+
devDependencies: {
|
|
3941
|
+
"@types/bun": "latest",
|
|
3942
|
+
typescript: "^5",
|
|
3943
|
+
"@opencode-ai/plugin": "*",
|
|
3944
|
+
"@opencode/plugin": "^2.0.11",
|
|
3945
|
+
"@ai-sdk/provider": "3.0.8"
|
|
3946
|
+
},
|
|
3947
|
+
files: [
|
|
3948
|
+
"dist",
|
|
3949
|
+
"tui.js",
|
|
3950
|
+
"LICENSE"
|
|
3951
|
+
],
|
|
3952
|
+
keywords: [
|
|
3953
|
+
"opencode",
|
|
3954
|
+
"command-code",
|
|
3955
|
+
"plugin",
|
|
3956
|
+
"provider",
|
|
3957
|
+
"ai-sdk"
|
|
3958
|
+
],
|
|
3959
|
+
license: "MIT",
|
|
3960
|
+
publishConfig: {
|
|
3961
|
+
access: "public"
|
|
3962
|
+
}
|
|
3963
|
+
};
|
|
3224
3964
|
|
|
3225
3965
|
// src/index.ts
|
|
3966
|
+
var PROVIDER_NPM = `${package_default.name}@${package_default.version}`;
|
|
3226
3967
|
function tool(input) {
|
|
3227
3968
|
return input;
|
|
3228
3969
|
}
|
|
@@ -3318,18 +4059,18 @@ var CommandCodePlugin = async (_input) => {
|
|
|
3318
4059
|
console.warn("[command-code] model list unavailable:", e);
|
|
3319
4060
|
}
|
|
3320
4061
|
}
|
|
3321
|
-
const claudeDefs = split ? toModelDefs(split.claude, "command-code-anthropic",
|
|
3322
|
-
const openDefs = split ? toModelDefs(split.open, "command-code-openai",
|
|
4062
|
+
const claudeDefs = split ? toModelDefs(split.claude, "command-code-anthropic", PROVIDER_NPM) : {};
|
|
4063
|
+
const openDefs = split ? toModelDefs(split.open, "command-code-openai", PROVIDER_NPM, { field: "reasoning_content" }) : {};
|
|
3323
4064
|
const userAnthropic = existing("command-code-anthropic");
|
|
3324
4065
|
cfg.provider["command-code-anthropic"] = {
|
|
3325
|
-
npm: userAnthropic.npm ??
|
|
4066
|
+
npm: userAnthropic.npm ?? PROVIDER_NPM,
|
|
3326
4067
|
name: userAnthropic.name ?? "Command Code (Anthropic)",
|
|
3327
4068
|
options: { baseURL: PROVIDER_BASE, ...userAnthropic.options },
|
|
3328
4069
|
models: { ...claudeDefs, ...userAnthropic.models }
|
|
3329
4070
|
};
|
|
3330
4071
|
const userOpenai = existing("command-code-openai");
|
|
3331
4072
|
cfg.provider["command-code-openai"] = {
|
|
3332
|
-
npm: userOpenai.npm ??
|
|
4073
|
+
npm: userOpenai.npm ?? PROVIDER_NPM,
|
|
3333
4074
|
name: userOpenai.name ?? "Command Code (OpenAI)",
|
|
3334
4075
|
options: {
|
|
3335
4076
|
baseURL: PROVIDER_BASE,
|
|
@@ -3352,7 +4093,7 @@ var CommandCodePlugin = async (_input) => {
|
|
|
3352
4093
|
return a?.type === "api" && a.key ? { key: a.key } : undefined;
|
|
3353
4094
|
});
|
|
3354
4095
|
const split = await loadModels(key);
|
|
3355
|
-
return toModelDefs(split.claude, "command-code-anthropic",
|
|
4096
|
+
return toModelDefs(split.claude, "command-code-anthropic", PROVIDER_NPM);
|
|
3356
4097
|
}
|
|
3357
4098
|
},
|
|
3358
4099
|
tool: {
|
|
@@ -3375,5 +4116,6 @@ var src_default = {
|
|
|
3375
4116
|
};
|
|
3376
4117
|
export {
|
|
3377
4118
|
CommandCodePlugin,
|
|
4119
|
+
createCommandCode,
|
|
3378
4120
|
src_default as default
|
|
3379
4121
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { LanguageModelV3 } from "@ai-sdk/provider";
|
|
2
|
+
import { type WireLane } from "./wire";
|
|
3
|
+
export declare const DEFAULT_BASE_URL = "https://api.commandcode.ai";
|
|
4
|
+
export interface CommandCodeProviderOptions {
|
|
5
|
+
/** Bearer key; the v2 host passes the resolved connection credential here. */
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
baseURL?: string;
|
|
8
|
+
/** Extra headers (host-supplied), merged last. */
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
/** Reasoning effort the host wants (`providerOptions.commandcode.reasoning`). */
|
|
11
|
+
reasoningEffort?: string;
|
|
12
|
+
/** Injected for tests. */
|
|
13
|
+
fetch?: typeof fetch;
|
|
14
|
+
}
|
|
15
|
+
export interface CommandCodeProvider {
|
|
16
|
+
languageModel(modelId: string): LanguageModelV3;
|
|
17
|
+
}
|
|
18
|
+
/** Claude models use the Anthropic wire; everything else chat completions. */
|
|
19
|
+
export declare function laneOf(modelId: string): WireLane;
|
|
20
|
+
/** True when the model advertises image input; the caller passes the table in. */
|
|
21
|
+
export type ImagesFor = (modelId: string) => boolean;
|
|
22
|
+
export declare function createCommandCode(options?: CommandCodeProviderOptions, images?: ImagesFor): CommandCodeProvider;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LanguageModelV3FinishReason, LanguageModelV3StreamPart, LanguageModelV3Usage } from "@ai-sdk/provider";
|
|
2
|
+
type Part = LanguageModelV3StreamPart;
|
|
3
|
+
/** Split an SSE byte stream into JSON events, tolerating partial chunks. */
|
|
4
|
+
export declare function sseDecoder(onEvent: (event: Record<string, unknown>) => void): (text: string) => void;
|
|
5
|
+
/** Wire stop reason → unified v3 finish reason; unknown reasons completed. */
|
|
6
|
+
export declare function finishReason(raw: string | undefined): LanguageModelV3FinishReason;
|
|
7
|
+
/** Anthropic `message_start`/`message_delta` usage → v3 usage. */
|
|
8
|
+
export declare function anthropicUsage(raw: Record<string, unknown>): LanguageModelV3Usage;
|
|
9
|
+
/** OpenAI usage → v3 usage (`prompt_tokens_details.cached_tokens` is cache read). */
|
|
10
|
+
export declare function openaiUsage(raw: Record<string, unknown>): LanguageModelV3Usage;
|
|
11
|
+
export interface Reducer {
|
|
12
|
+
/** Feed one parsed SSE event; returns the parts it produced. */
|
|
13
|
+
feed(event: Record<string, unknown>): Part[];
|
|
14
|
+
/** Close open parts when the transport ends (abort/error). */
|
|
15
|
+
close(): Part[];
|
|
16
|
+
}
|
|
17
|
+
/** Anthropic Messages reducer. */
|
|
18
|
+
export declare function anthropicReducer(): Reducer;
|
|
19
|
+
/** OpenAI Chat Completions reducer. */
|
|
20
|
+
export declare function openaiReducer(): Reducer;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { LanguageModelV3Prompt } from "@ai-sdk/provider";
|
|
2
|
+
export type WireLane = "anthropic" | "openai";
|
|
3
|
+
/** The subset of tools we serialize (V3 FunctionTool already matches). */
|
|
4
|
+
export interface WireTool {
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
inputSchema: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface WireOptions {
|
|
10
|
+
maxOutputTokens?: number;
|
|
11
|
+
temperature?: number;
|
|
12
|
+
tools?: WireTool[];
|
|
13
|
+
/** Wire extension both gates honour (`reasoning_effort`). */
|
|
14
|
+
reasoningEffort?: string;
|
|
15
|
+
/** Whether the model advertises image input (catalog-derived). */
|
|
16
|
+
images: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** Anthropic body: POST /provider/v1/messages. */
|
|
19
|
+
export declare function anthropicBody(model: string, prompt: LanguageModelV3Prompt, options: WireOptions): Record<string, unknown>;
|
|
20
|
+
/** OpenAI body: POST /provider/v1/chat/completions. */
|
|
21
|
+
export declare function openaiBody(model: string, prompt: LanguageModelV3Prompt, options: WireOptions): Record<string, unknown>;
|
|
22
|
+
export declare function requestBody(lane: WireLane, model: string, prompt: LanguageModelV3Prompt, options: WireOptions): Record<string, unknown>;
|
|
@@ -8,3 +8,9 @@ export declare const WEEKLY_SECS: number;
|
|
|
8
8
|
* the same unit cmduse-core's vectors pin.
|
|
9
9
|
*/
|
|
10
10
|
export declare function elapsedPct(resetAtMs: number | undefined, durSecs: number, nowSecs: number): number | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* `elapsedPct` as text: a seven-day window forty minutes in is 0.4%, and "0%"
|
|
13
|
+
* reads as "not started". Non-zero fractions below a percent render "<1%".
|
|
14
|
+
* Exact zero stays "0%".
|
|
15
|
+
*/
|
|
16
|
+
export declare function elapsedLabel(resetAtMs: number | undefined, durSecs: number, nowSecs: number): string | undefined;
|
package/dist/tui.js
CHANGED
|
@@ -403,6 +403,14 @@ function elapsedPct(resetAtMs, durSecs, nowSecs) {
|
|
|
403
403
|
const pct = (nowSecs - start) / durSecs * 100;
|
|
404
404
|
return Math.round(Math.min(100, Math.max(0, pct)));
|
|
405
405
|
}
|
|
406
|
+
function elapsedLabel(resetAtMs, durSecs, nowSecs) {
|
|
407
|
+
const pct = elapsedPct(resetAtMs, durSecs, nowSecs);
|
|
408
|
+
if (pct === undefined)
|
|
409
|
+
return;
|
|
410
|
+
if (pct === 0 && nowSecs > Math.floor(resetAtMs / 1000) - durSecs)
|
|
411
|
+
return "<1%";
|
|
412
|
+
return `${pct}%`;
|
|
413
|
+
}
|
|
406
414
|
|
|
407
415
|
// src/sidebar/rows.ts
|
|
408
416
|
var TIER_DISPLAY = {
|
|
@@ -445,9 +453,9 @@ function windowRow(label, w, now, durSecs) {
|
|
|
445
453
|
return;
|
|
446
454
|
const used = typeof w.used === "number" ? w.used : 0;
|
|
447
455
|
const pct = Math.round(used / w.cap * 100);
|
|
448
|
-
const elapsed =
|
|
456
|
+
const elapsed = elapsedLabel(w.resetAt, durSecs, Math.floor(now / 1000));
|
|
449
457
|
const reset = until(w.resetAt, now);
|
|
450
|
-
const parts = [`${money(used)} / ${money(w.cap)} (${pct}%)`, ...elapsed === undefined ? [] : [`${elapsed}
|
|
458
|
+
const parts = [`${money(used)} / ${money(w.cap)} (${pct}%)`, ...elapsed === undefined ? [] : [`${elapsed} elapsed`], ...reset ? [`resets ${reset}`] : []];
|
|
451
459
|
return [label, parts.join(" \xB7 ")];
|
|
452
460
|
}
|
|
453
461
|
function usageRows(usage, now = Date.now()) {
|
package/dist/v2.d.ts
CHANGED
|
@@ -21,9 +21,15 @@ export declare const TOOL_DESCRIPTION = "Fetch live Command Code plan/usage: pla
|
|
|
21
21
|
* only the open lane needs the reasoning round-trip field. */
|
|
22
22
|
export declare function toV2Model(m: CmdModel, lane: Lane): unknown;
|
|
23
23
|
/** Instant static seed from gating.json's known model ids, split by wire lane.
|
|
24
|
-
*
|
|
25
|
-
* fallback shown when the live fetch fails. */
|
|
24
|
+
* Shows in /model immediately; the live fetch merges over it later. */
|
|
26
25
|
export declare function staticSeedModels(): Record<Lane["id"], unknown[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Union by model id: the snapshot keeps every id it had, so a gated or partial
|
|
28
|
+
* live response can never make a model vanish from the picker, while live
|
|
29
|
+
* entries win field-by-field so context, rates, variants and modalities stay
|
|
30
|
+
* fresh. Snapshot order is preserved; live-only models append in live order.
|
|
31
|
+
*/
|
|
32
|
+
export declare function mergeModels(snapshot: readonly unknown[], live: readonly unknown[]): unknown[];
|
|
27
33
|
/** The slice of the plugin context the credential resolver needs. */
|
|
28
34
|
type CredentialContext = {
|
|
29
35
|
integration: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffreyjyz/opencode-command-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Command Code provider + usage dashboard for opencode: two providers (Anthropic wire for Claude, OpenAI wire for everything else) against api.commandcode.ai, live model list, plan gating, /cmd-usage. Requires the cmduse CLI for usage rendering.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"@types/bun": "latest",
|
|
27
27
|
"typescript": "^5",
|
|
28
28
|
"@opencode-ai/plugin": "*",
|
|
29
|
-
"@opencode/plugin": "^2.0.11"
|
|
29
|
+
"@opencode/plugin": "^2.0.11",
|
|
30
|
+
"@ai-sdk/provider": "3.0.8"
|
|
30
31
|
},
|
|
31
32
|
"files": [
|
|
32
33
|
"dist",
|