@darthph0enix/commandcode-go-opencode-provider 0.5.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/LICENSE +21 -0
- package/README.md +121 -0
- package/index.ts +28 -0
- package/models.json +2954 -0
- package/package.json +61 -0
- package/plugin.ts +75 -0
- package/src/auth.ts +42 -0
- package/src/convert.ts +308 -0
- package/src/model-sync.ts +345 -0
- package/src/model.ts +159 -0
- package/src/stream.ts +188 -0
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@darthph0enix/commandcode-go-opencode-provider",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"author": "Brent Weatherall",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/Darthph0enix7/opencode-commandcode-provider.git"
|
|
8
|
+
},
|
|
9
|
+
"main": "index.ts",
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@types/node": "^25.9.1",
|
|
12
|
+
"ai": "6.0.191",
|
|
13
|
+
"typescript": "^6.0.3"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": "./index.ts"
|
|
18
|
+
},
|
|
19
|
+
"./server": {
|
|
20
|
+
"import": "./plugin.ts"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/Darthph0enix7/opencode-commandcode-provider/issues"
|
|
25
|
+
},
|
|
26
|
+
"description": "Command Code API provider for opencode — use Claude, GPT, Gemini, DeepSeek, Qwen, Kimi, GLM, MiniMax, and Step models via Command Code",
|
|
27
|
+
"engines": { "bun": ">=1.0.0" },
|
|
28
|
+
"files": ["index.ts", "plugin.ts", "models.json", "src/"],
|
|
29
|
+
"homepage": "https://github.com/Darthph0enix7/opencode-commandcode-provider#readme",
|
|
30
|
+
"keywords": [
|
|
31
|
+
"opencode",
|
|
32
|
+
"opencode-provider",
|
|
33
|
+
"opencode-plugin",
|
|
34
|
+
"commandcode",
|
|
35
|
+
"ai",
|
|
36
|
+
"llm",
|
|
37
|
+
"claude",
|
|
38
|
+
"gpt",
|
|
39
|
+
"gemini",
|
|
40
|
+
"deepseek",
|
|
41
|
+
"qwen",
|
|
42
|
+
"kimi",
|
|
43
|
+
"glm",
|
|
44
|
+
"minimax"
|
|
45
|
+
],
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"scripts": {
|
|
48
|
+
"test": "bun test tests/unit/",
|
|
49
|
+
"test:integration": "bun test tests/integration/",
|
|
50
|
+
"test:all": "bun test",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"generate-readme": "bun run scripts/generate-readme.ts",
|
|
53
|
+
"sync": "bun run scripts/sync-models.ts",
|
|
54
|
+
"sync:global": "bun run scripts/sync-models.ts --update-global"
|
|
55
|
+
},
|
|
56
|
+
"type": "module",
|
|
57
|
+
"types": "index.ts",
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"ai": ">=6.0.0"
|
|
60
|
+
}
|
|
61
|
+
}
|
package/plugin.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { loadSyncedModels } from "./src/model-sync.js"
|
|
2
|
+
|
|
3
|
+
function toConfigKey(id: string): string {
|
|
4
|
+
const slashIdx = id.indexOf("/")
|
|
5
|
+
const short = slashIdx >= 0 ? id.slice(slashIdx + 1) : id
|
|
6
|
+
return short.toLowerCase()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default async function commandcodePlugin() {
|
|
10
|
+
return {
|
|
11
|
+
config: async (config: Record<string, unknown>) => {
|
|
12
|
+
const providers = config.provider as Record<string, Record<string, unknown>> | undefined
|
|
13
|
+
if (!providers) {
|
|
14
|
+
(config as Record<string, unknown>).provider = { commandcode: {} }
|
|
15
|
+
}
|
|
16
|
+
const cc = ((config as Record<string, unknown>).provider as Record<string, Record<string, unknown>>)?.commandcode as Record<string, unknown> | undefined
|
|
17
|
+
if (!cc) return
|
|
18
|
+
|
|
19
|
+
if (!cc.npm) cc.npm = "commandcode-go-opencode-provider"
|
|
20
|
+
if (!cc.name) cc.name = "Command Code"
|
|
21
|
+
if (!cc.env) cc.env = ["COMMANDCODE_API_KEY"]
|
|
22
|
+
|
|
23
|
+
if (!cc.models) {
|
|
24
|
+
const { models } = await loadSyncedModels()
|
|
25
|
+
const modelsObj: Record<string, unknown> = {}
|
|
26
|
+
for (const entry of models) {
|
|
27
|
+
const key = toConfigKey(entry.id)
|
|
28
|
+
const costObj: Record<string, number> = { input: entry.cost.input, output: entry.cost.output }
|
|
29
|
+
if (entry.cost.cache_read !== undefined) costObj.cache_read = entry.cost.cache_read
|
|
30
|
+
if (entry.cost.cache_write !== undefined) costObj.cache_write = entry.cost.cache_write
|
|
31
|
+
|
|
32
|
+
modelsObj[key] = {
|
|
33
|
+
id: entry.id,
|
|
34
|
+
name: entry.name,
|
|
35
|
+
reasoning: entry.reasoning,
|
|
36
|
+
tool_call: entry.tool_call,
|
|
37
|
+
attachment: entry.attachment ?? (entry.modalities?.input?.some((m) => m !== "text") ?? false),
|
|
38
|
+
modalities: entry.modalities ?? { input: ["text"], output: ["text"] },
|
|
39
|
+
variants: entry.variants,
|
|
40
|
+
cost: costObj,
|
|
41
|
+
limit: entry.limit,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
cc.models = modelsObj
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
auth: {
|
|
49
|
+
provider: "commandcode",
|
|
50
|
+
methods: [
|
|
51
|
+
{
|
|
52
|
+
type: "api",
|
|
53
|
+
label: "API Key",
|
|
54
|
+
authorize: async (inputs: Record<string, unknown> | undefined) => {
|
|
55
|
+
const rawKey = inputs?.key
|
|
56
|
+
if (typeof rawKey !== "string") return { type: "failed" as const }
|
|
57
|
+
const key = rawKey.trim()
|
|
58
|
+
if (!key) return { type: "failed" as const }
|
|
59
|
+
return { type: "success" as const, key }
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
loader: async (getAuth: () => Promise<{ type: string; key?: string } | null>) => {
|
|
64
|
+
try {
|
|
65
|
+
const auth = await getAuth()
|
|
66
|
+
if (!auth) return {}
|
|
67
|
+
if (auth.type === "api" && auth.key) return { apiKey: auth.key }
|
|
68
|
+
return {}
|
|
69
|
+
} catch {
|
|
70
|
+
return {}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "fs"
|
|
2
|
+
import { homedir } from "os"
|
|
3
|
+
import { join } from "path"
|
|
4
|
+
|
|
5
|
+
export function resolveApiKey(options: {
|
|
6
|
+
apiKey?: string
|
|
7
|
+
env?: Record<string, string | undefined>
|
|
8
|
+
}): string | undefined {
|
|
9
|
+
if (options.apiKey) return options.apiKey
|
|
10
|
+
|
|
11
|
+
const envKey = options.env?.COMMANDCODE_API_KEY ?? process.env.COMMANDCODE_API_KEY
|
|
12
|
+
if (envKey) return envKey
|
|
13
|
+
|
|
14
|
+
const authPaths = [
|
|
15
|
+
join(homedir(), ".commandcode", "auth.json"),
|
|
16
|
+
join(homedir(), ".pi", "agent", "auth.json"),
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
for (const p of authPaths) {
|
|
20
|
+
if (!existsSync(p)) continue
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(readFileSync(p, "utf-8"))
|
|
23
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
24
|
+
if (typeof parsed.apiKey === "string") return parsed.apiKey
|
|
25
|
+
if (typeof parsed.commandcode === "string") return parsed.commandcode
|
|
26
|
+
if (
|
|
27
|
+
typeof parsed.commandcode === "object" &&
|
|
28
|
+
parsed.commandcode !== null &&
|
|
29
|
+
parsed.commandcode.type === "oauth" &&
|
|
30
|
+
typeof parsed.commandcode.access === "string"
|
|
31
|
+
) {
|
|
32
|
+
return parsed.commandcode.access
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
// intentionally silent: skip unreadable or malformed auth files
|
|
37
|
+
continue
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return undefined
|
|
42
|
+
}
|
package/src/convert.ts
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import type { LanguageModelV3CallOptions } from "@ai-sdk/provider"
|
|
2
|
+
import type {
|
|
3
|
+
LanguageModelV3FunctionTool,
|
|
4
|
+
LanguageModelV3Message,
|
|
5
|
+
LanguageModelV3TextPart,
|
|
6
|
+
LanguageModelV3ReasoningPart,
|
|
7
|
+
LanguageModelV3ToolCallPart,
|
|
8
|
+
LanguageModelV3ToolResultPart,
|
|
9
|
+
LanguageModelV3ToolResultOutput,
|
|
10
|
+
} from "@ai-sdk/provider"
|
|
11
|
+
|
|
12
|
+
type CCMessage =
|
|
13
|
+
| { role: "user"; content: string | unknown[] }
|
|
14
|
+
| { role: "assistant"; content: CCAssistantContent[] }
|
|
15
|
+
| { role: "tool"; content: CCToolResultContent[] }
|
|
16
|
+
|
|
17
|
+
type CCAssistantContent =
|
|
18
|
+
| { type: "text"; text: string }
|
|
19
|
+
| { type: "reasoning"; text: string }
|
|
20
|
+
| { type: "tool-call"; toolCallId: string; toolName: string; input: unknown }
|
|
21
|
+
|
|
22
|
+
type CCToolResultContent = {
|
|
23
|
+
type: "tool-result"
|
|
24
|
+
toolCallId: string
|
|
25
|
+
toolName: string
|
|
26
|
+
output: { type: "text"; value: string } | { type: "error-text"; value: string }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type CCTool = {
|
|
30
|
+
type: "function"
|
|
31
|
+
name: string
|
|
32
|
+
description?: string
|
|
33
|
+
input_schema: unknown
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface CCRequestEnvelope {
|
|
37
|
+
config: {
|
|
38
|
+
workingDir: string
|
|
39
|
+
date: string
|
|
40
|
+
environment: string
|
|
41
|
+
structure: unknown[]
|
|
42
|
+
isGitRepo: boolean
|
|
43
|
+
currentBranch: string
|
|
44
|
+
mainBranch: string
|
|
45
|
+
gitStatus: string
|
|
46
|
+
recentCommits: unknown[]
|
|
47
|
+
}
|
|
48
|
+
memory: string
|
|
49
|
+
taste: string
|
|
50
|
+
skills: null
|
|
51
|
+
permissionMode: string
|
|
52
|
+
params: {
|
|
53
|
+
model: string
|
|
54
|
+
messages: CCMessage[]
|
|
55
|
+
tools: CCTool[]
|
|
56
|
+
system: string
|
|
57
|
+
max_tokens: number
|
|
58
|
+
stream: true
|
|
59
|
+
temperature?: number
|
|
60
|
+
top_p?: number
|
|
61
|
+
top_k?: number
|
|
62
|
+
reasoning_effort?: string
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function hasType(p: unknown, type: string): boolean {
|
|
67
|
+
return typeof p === "object" && p !== null && (p as { type?: string }).type === type
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isTextPart(p: unknown): p is LanguageModelV3TextPart {
|
|
71
|
+
return hasType(p, "text")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isReasoningPart(p: unknown): p is LanguageModelV3ReasoningPart {
|
|
75
|
+
return hasType(p, "reasoning")
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isToolCallPart(p: unknown): p is LanguageModelV3ToolCallPart {
|
|
79
|
+
return hasType(p, "tool-call")
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isToolResultPart(p: unknown): p is LanguageModelV3ToolResultPart {
|
|
83
|
+
return hasType(p, "tool-result")
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function extractText(content: unknown): string {
|
|
87
|
+
if (typeof content === "string") return content
|
|
88
|
+
if (Array.isArray(content)) {
|
|
89
|
+
const textParts = content.filter(isTextPart) as LanguageModelV3TextPart[]
|
|
90
|
+
return textParts.map((p) => p.text).join("\n")
|
|
91
|
+
}
|
|
92
|
+
return ""
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function toDataUriOrUrl(data: string | Uint8Array | URL, defaultMime: string = "image/png"): string {
|
|
96
|
+
if (typeof data === "string") {
|
|
97
|
+
if (data.startsWith("data:") || data.startsWith("http://") || data.startsWith("https://")) {
|
|
98
|
+
return data
|
|
99
|
+
}
|
|
100
|
+
return `data:${defaultMime};base64,${data}`
|
|
101
|
+
}
|
|
102
|
+
if (data instanceof URL) {
|
|
103
|
+
return data.toString()
|
|
104
|
+
}
|
|
105
|
+
if (data instanceof Uint8Array || (typeof Buffer !== "undefined" && Buffer.isBuffer(data))) {
|
|
106
|
+
const b64 = Buffer.from(data).toString("base64")
|
|
107
|
+
return `data:${defaultMime};base64,${b64}`
|
|
108
|
+
}
|
|
109
|
+
return String(data)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function convertUserContent(content: unknown): string | Array<{ type: string; [key: string]: unknown }> {
|
|
113
|
+
if (typeof content === "string") return content
|
|
114
|
+
if (!Array.isArray(content)) return ""
|
|
115
|
+
|
|
116
|
+
const parts: Array<{ type: string; [key: string]: unknown }> = []
|
|
117
|
+
let hasMultimodal = false
|
|
118
|
+
|
|
119
|
+
for (const p of content) {
|
|
120
|
+
if (!p || typeof p !== "object") continue
|
|
121
|
+
const item = p as Record<string, unknown>
|
|
122
|
+
|
|
123
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
124
|
+
parts.push({ type: "text", text: item.text })
|
|
125
|
+
} else if (item.type === "image" && item.image) {
|
|
126
|
+
hasMultimodal = true
|
|
127
|
+
const mimeType = typeof item.mimeType === "string" ? item.mimeType : "image/png"
|
|
128
|
+
const imgUrl = toDataUriOrUrl(item.image as string | Uint8Array | URL, mimeType)
|
|
129
|
+
parts.push({ type: "image", image: imgUrl, mimeType })
|
|
130
|
+
} else if (item.type === "file" && item.data) {
|
|
131
|
+
const mimeType = typeof item.mimeType === "string" ? item.mimeType : "application/octet-stream"
|
|
132
|
+
if (mimeType.startsWith("image/")) {
|
|
133
|
+
hasMultimodal = true
|
|
134
|
+
const imgUrl = toDataUriOrUrl(item.data as string | Uint8Array | URL, mimeType)
|
|
135
|
+
parts.push({ type: "image", image: imgUrl, mimeType })
|
|
136
|
+
} else if (typeof item.data === "string" && !item.data.startsWith("data:")) {
|
|
137
|
+
parts.push({ type: "text", text: item.data })
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!hasMultimodal) {
|
|
143
|
+
return parts.map((p) => (p.type === "text" ? String(p.text) : "")).join("\n")
|
|
144
|
+
}
|
|
145
|
+
return parts
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function convertToolResultOutput(output: LanguageModelV3ToolResultOutput): CCToolResultContent["output"] {
|
|
149
|
+
switch (output.type) {
|
|
150
|
+
case "text":
|
|
151
|
+
return { type: "text", value: output.value }
|
|
152
|
+
case "error-text":
|
|
153
|
+
return { type: "error-text", value: output.value }
|
|
154
|
+
case "json":
|
|
155
|
+
return { type: "text", value: JSON.stringify(output.value) }
|
|
156
|
+
case "execution-denied":
|
|
157
|
+
return { type: "error-text", value: output.reason ?? "Execution denied" }
|
|
158
|
+
case "error-json":
|
|
159
|
+
return { type: "error-text", value: JSON.stringify(output.value) }
|
|
160
|
+
case "content":
|
|
161
|
+
return { type: "text", value: output.value.map((v: Record<string, unknown>) => ("text" in v ? v.text : JSON.stringify(v))).join("\n") }
|
|
162
|
+
default:
|
|
163
|
+
return { type: "text", value: JSON.stringify(output) }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function convertMessage(msg: LanguageModelV3Message): CCMessage | null {
|
|
168
|
+
switch (msg.role) {
|
|
169
|
+
case "user": {
|
|
170
|
+
const content = convertUserContent(msg.content)
|
|
171
|
+
return { role: "user", content: content as any }
|
|
172
|
+
}
|
|
173
|
+
case "assistant": {
|
|
174
|
+
const parts: CCAssistantContent[] = []
|
|
175
|
+
for (const part of msg.content) {
|
|
176
|
+
if (isTextPart(part)) {
|
|
177
|
+
parts.push({ type: "text", text: part.text })
|
|
178
|
+
} else if (isReasoningPart(part)) {
|
|
179
|
+
parts.push({ type: "reasoning", text: part.text })
|
|
180
|
+
} else if (isToolCallPart(part)) {
|
|
181
|
+
parts.push({
|
|
182
|
+
type: "tool-call",
|
|
183
|
+
toolCallId: part.toolCallId,
|
|
184
|
+
toolName: part.toolName,
|
|
185
|
+
input: part.input,
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { role: "assistant", content: parts }
|
|
190
|
+
}
|
|
191
|
+
case "tool": {
|
|
192
|
+
const parts: CCToolResultContent[] = []
|
|
193
|
+
for (const part of msg.content) {
|
|
194
|
+
if (isToolResultPart(part)) {
|
|
195
|
+
parts.push({
|
|
196
|
+
type: "tool-result",
|
|
197
|
+
toolCallId: part.toolCallId,
|
|
198
|
+
toolName: part.toolName,
|
|
199
|
+
output: convertToolResultOutput(part.output),
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { role: "tool", content: parts }
|
|
204
|
+
}
|
|
205
|
+
default:
|
|
206
|
+
return null
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function convertTools(
|
|
211
|
+
tools: Array<LanguageModelV3FunctionTool | { type: "provider"; id: `${string}.${string}`; name: string; args: Record<string, unknown> }> | undefined,
|
|
212
|
+
): CCTool[] {
|
|
213
|
+
if (!tools) return []
|
|
214
|
+
return tools
|
|
215
|
+
.filter((t): t is LanguageModelV3FunctionTool => t.type === "function")
|
|
216
|
+
.map((t) => ({
|
|
217
|
+
type: "function" as const,
|
|
218
|
+
name: t.name,
|
|
219
|
+
description: t.description,
|
|
220
|
+
input_schema: t.inputSchema,
|
|
221
|
+
}))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function extractReasoningEffort(options: LanguageModelV3CallOptions): string | undefined {
|
|
225
|
+
const opt = options as Record<string, unknown>
|
|
226
|
+
if (typeof opt.reasoning_effort === "string") return opt.reasoning_effort
|
|
227
|
+
if (typeof opt.reasoningEffort === "string") return opt.reasoningEffort
|
|
228
|
+
if (typeof opt.effort === "string") return opt.effort
|
|
229
|
+
if (typeof opt.variant === "string") return opt.variant
|
|
230
|
+
|
|
231
|
+
if (opt.providerOptions && typeof opt.providerOptions === "object") {
|
|
232
|
+
const po = opt.providerOptions as Record<string, unknown>
|
|
233
|
+
for (const key of Object.keys(po)) {
|
|
234
|
+
const val = po[key]
|
|
235
|
+
if (val && typeof val === "object") {
|
|
236
|
+
const sub = val as Record<string, unknown>
|
|
237
|
+
if (typeof sub.reasoning_effort === "string") return sub.reasoning_effort
|
|
238
|
+
if (typeof sub.reasoningEffort === "string") return sub.reasoningEffort
|
|
239
|
+
if (typeof sub.effort === "string") return sub.effort
|
|
240
|
+
if (typeof sub.variant === "string") return sub.variant
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (opt.options && typeof opt.options === "object") {
|
|
246
|
+
const sub = opt.options as Record<string, unknown>
|
|
247
|
+
if (typeof sub.reasoning_effort === "string") return sub.reasoning_effort
|
|
248
|
+
if (typeof sub.reasoningEffort === "string") return sub.reasoningEffort
|
|
249
|
+
if (typeof sub.effort === "string") return sub.effort
|
|
250
|
+
if (typeof sub.variant === "string") return sub.variant
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return undefined
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function buildRequest(
|
|
257
|
+
modelId: string,
|
|
258
|
+
options: LanguageModelV3CallOptions,
|
|
259
|
+
): CCRequestEnvelope {
|
|
260
|
+
let systemPrompt = ""
|
|
261
|
+
const messages: CCMessage[] = []
|
|
262
|
+
|
|
263
|
+
for (const msg of options.prompt) {
|
|
264
|
+
if (msg.role === "system") {
|
|
265
|
+
systemPrompt += (systemPrompt ? "\n\n" : "") + msg.content
|
|
266
|
+
continue
|
|
267
|
+
}
|
|
268
|
+
const converted = convertMessage(msg)
|
|
269
|
+
if (converted) messages.push(converted)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const effort = extractReasoningEffort(options)
|
|
273
|
+
|
|
274
|
+
const params: CCRequestEnvelope["params"] = {
|
|
275
|
+
model: modelId,
|
|
276
|
+
messages,
|
|
277
|
+
tools: convertTools(options.tools),
|
|
278
|
+
system: systemPrompt,
|
|
279
|
+
max_tokens: options.maxOutputTokens ?? 16384,
|
|
280
|
+
stream: true,
|
|
281
|
+
...(effort ? { reasoning_effort: effort } : {}),
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (options.temperature !== undefined) params.temperature = options.temperature
|
|
285
|
+
if (options.topP !== undefined) params.top_p = options.topP
|
|
286
|
+
if (options.topK !== undefined) params.top_k = options.topK
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
config: {
|
|
290
|
+
workingDir: process.cwd() ?? "/",
|
|
291
|
+
date: new Date().toISOString().split("T")[0] ?? "",
|
|
292
|
+
environment: `${process.platform}-${process.arch}`,
|
|
293
|
+
// Stub: opencode does not expose project structure context
|
|
294
|
+
structure: [],
|
|
295
|
+
isGitRepo: false,
|
|
296
|
+
currentBranch: "",
|
|
297
|
+
mainBranch: "",
|
|
298
|
+
gitStatus: "",
|
|
299
|
+
recentCommits: [],
|
|
300
|
+
},
|
|
301
|
+
memory: "",
|
|
302
|
+
// Stub: taste/memory/permissionMode are Command Code CLI features not exposed via provider API
|
|
303
|
+
taste: "",
|
|
304
|
+
skills: null,
|
|
305
|
+
permissionMode: "standard",
|
|
306
|
+
params,
|
|
307
|
+
}
|
|
308
|
+
}
|