Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions models.json
Original file line number Diff line number Diff line change
Expand Up @@ -336,5 +336,114 @@
"context": 1000000,
"output": 131072
}
},
{
"id": "MiniMaxAI/MiniMax-M3",
"name": "MiniMax M3",
"tier": "open-source",
"reasoning": true,
"tool_call": true,
"cost": {
"input": 0.3,
"output": 1.2,
"cache_read": 0.06
},
"limit": {
"context": 1000000,
"output": 131072
}
},
{
"id": "moonshotai/Kimi-K2.7-Code",
"name": "Kimi K2.7 Code",
"tier": "open-source",
"reasoning": true,
"tool_call": true,
"cost": {
"input": 0.95,
"output": 4,
"cache_read": 0.16
},
"limit": {
"context": 256000,
"output": 131072
}
},
{
"id": "Qwen/Qwen3.7-Plus",
"name": "Qwen 3.7 Plus",
"tier": "open-source",
"reasoning": true,
"tool_call": true,
"cost": {
"input": 0.5,
"output": 3,
"cache_read": 0.1
},
"limit": {
"context": 1000000,
"output": 131072
}
},
{
"id": "stepfun/Step-3.7-Flash",
"name": "Step 3.7 Flash",
"tier": "open-source",
"reasoning": true,
"tool_call": true,
"cost": {
"input": 0.1,
"output": 0.3,
"cache_read": 0.02
},
"limit": {
"context": 256000,
"output": 131072
}
},
{
"id": "nvidia/nemotron-3-ultra-550b-a55b",
"name": "Nemotron 3 Ultra",
"tier": "open-source",
"reasoning": true,
"tool_call": true,
"cost": {
"input": 0.35,
"output": 1.4
},
"limit": {
"context": 1000000,
"output": 131072
}
},
{
"id": "xiaomi/mimo-v2.5",
"name": "MiMo V2.5",
"tier": "open-source",
"reasoning": true,
"tool_call": true,
"cost": {
"input": 0.4,
"output": 1.6
},
"limit": {
"context": 1000000,
"output": 131072
}
},
{
"id": "xiaomi/mimo-v2.5-pro",
"name": "MiMo V2.5 Pro",
"tier": "open-source",
"reasoning": true,
"tool_call": true,
"cost": {
"input": 0.8,
"output": 3.2
},
"limit": {
"context": 1000000,
"output": 131072
}
}
]
152 changes: 152 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { readdirSync, statSync, readFileSync, existsSync } from "fs"
import { join, relative } from "path"
import { execSync } from "child_process"

interface GitContext {
isGitRepo: boolean
currentBranch: string
mainBranch: string
gitStatus: string
recentCommits: string[]
}

export interface ProjectContext {
structure: string[]
git: GitContext
}

const DEFAULT_SKIP = new Set([
"node_modules",
".git",
"dist",
"build",
".next",
".turbo",
"coverage",
".nyc_output",
"tmp",
"temp",
".cache",
".DS_Store",
"__pycache__",
".venv",
"venv",
".idea",
".vscode",
"target",
"out",
])

const MAX_FILES = 200
const MAX_DEPTH = 5

function parseGitignore(rootDir: string): Set<string> {
const p = join(rootDir, ".gitignore")
if (!existsSync(p)) return new Set()
try {
const patterns = new Set<string>()
for (const line of readFileSync(p, "utf-8").split("\n")) {
const t = line.trim()
if (t && !t.startsWith("#")) patterns.add(t)
}
return patterns
} catch {
return new Set()
}
}

function shouldSkip(name: string, relPath: string, gitignore: Set<string>): boolean {
if (DEFAULT_SKIP.has(name)) return true
for (const pat of gitignore) {
const p = pat.endsWith("/") ? pat.slice(0, -1) : pat
if (p === relPath || relPath.startsWith(p + "/") || p === name) return true
if (pat.includes("*")) {
const re = new RegExp("^" + pat.replace(/\./g, "\\.").replace(/\*/g, "[^/]*") + "$")
if (re.test(name)) return true
}
}
return false
}

function gatherStructure(rootDir: string): string[] {
const entries: string[] = []
const gitignore = parseGitignore(rootDir)

function walk(dir: string, depth: number) {
if (entries.length >= MAX_FILES || depth > MAX_DEPTH) return

let items: string[]
try {
items = readdirSync(dir)
} catch {
return
}

for (const name of items.sort()) {
const fullPath = join(dir, name)
const relPath = relative(rootDir, fullPath)
if (shouldSkip(name, relPath, gitignore)) continue

let st
try {
st = statSync(fullPath)
} catch {
continue
}

if (st.isDirectory()) {
entries.push(relPath + "/")
walk(fullPath, depth + 1)
} else if (st.isFile()) {
entries.push(relPath)
}
}
}

walk(rootDir, 0)
return entries
}

function runGit(cmd: string, cwd: string): string {
try {
return execSync(cmd, { cwd, encoding: "utf-8", stdio: "pipe", maxBuffer: 1024 * 1024 }).trim()
} catch {
return ""
}
}

function gatherGit(rootDir: string): GitContext {
try {
execSync("git rev-parse --is-inside-work-tree", { cwd: rootDir, stdio: "pipe" })
} catch {
return { isGitRepo: false, currentBranch: "", mainBranch: "", gitStatus: "", recentCommits: [] }
}

const currentBranch = runGit("git branch --show-current", rootDir)

let mainBranch = ""
for (const cand of ["main", "master"]) {
try {
execSync(`git rev-parse --verify ${cand}`, { cwd: rootDir, stdio: "pipe" })
mainBranch = cand
break
} catch { /* not found */ }
}

const gitStatus = runGit("git status --short", rootDir)

let recentCommits: string[] = []
const log = runGit('git log --oneline -10 --format="%h %s (%an, %ad)" --date=short', rootDir)
if (log) {
recentCommits = log.split("\n")
}

return { isGitRepo: true, currentBranch, mainBranch, gitStatus, recentCommits }
}

export function gatherContext(rootDir: string = process.cwd()): ProjectContext {
return {
structure: gatherStructure(rootDir),
git: gatherGit(rootDir),
}
}
17 changes: 9 additions & 8 deletions src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
LanguageModelV3ToolResultPart,
LanguageModelV3ToolResultOutput,
} from "@ai-sdk/provider"
import type { ProjectContext } from "./context.js"

type CCMessage =
| { role: "user"; content: string | unknown[] }
Expand Down Expand Up @@ -174,6 +175,7 @@ function convertTools(
export function buildRequest(
modelId: string,
options: LanguageModelV3CallOptions,
context?: ProjectContext,
): CCRequestEnvelope {
let systemPrompt = ""
const messages: CCMessage[] = []
Expand All @@ -200,21 +202,20 @@ export function buildRequest(
if (options.topP !== undefined) params.top_p = options.topP
if (options.topK !== undefined) params.top_k = options.topK

const ctx = context
return {
config: {
workingDir: process.cwd() ?? "/",
date: new Date().toISOString().split("T")[0] ?? "",
environment: `${process.platform}-${process.arch}`,
// Stub: opencode does not expose project structure context
structure: [],
isGitRepo: false,
currentBranch: "",
mainBranch: "",
gitStatus: "",
recentCommits: [],
structure: ctx?.structure ?? [],
isGitRepo: ctx?.git.isGitRepo ?? false,
currentBranch: ctx?.git.currentBranch ?? "",
mainBranch: ctx?.git.mainBranch ?? "",
gitStatus: ctx?.git.gitStatus ?? "",
recentCommits: ctx?.git.recentCommits ?? [],
},
memory: "",
// Stub: taste/memory/permissionMode are Command Code CLI features not exposed via provider API
taste: "",
skills: null,
permissionMode: "standard",
Expand Down
4 changes: 3 additions & 1 deletion src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
} from "@ai-sdk/provider"
import { buildRequest } from "./convert.js"
import { parseStreamEvents } from "./stream.js"
import { gatherContext } from "./context.js"

const DEFAULT_BASE_URL = "https://api.commandcode.ai"
// x-command-code-version must match the Command Code CLI version for API compatibility
Expand Down Expand Up @@ -49,7 +50,8 @@ export class CommandCodeLanguageModel implements LanguageModelV3 {
}

async doStream(options: LanguageModelV3CallOptions): Promise<LanguageModelV3StreamResult> {
const body = buildRequest(this.modelId, options)
const context = gatherContext()
const body = buildRequest(this.modelId, options, context)
const requestBody = JSON.stringify(body)

const controller = new AbortController()
Expand Down