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
2 changes: 1 addition & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 24 additions & 20 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,26 +434,30 @@ const layer = Layer.effect(

yield* ensureGitignore(dir).pipe(Effect.orDie)

const dep = yield* npmSvc
.install(dir, {
add: [
{
name: "@opencode-ai/plugin",
version: InstallationLocal ? undefined : InstallationVersion,
},
],
})
.pipe(
Effect.exit,
Effect.tap((exit) =>
Exit.isFailure(exit)
? Effect.logWarning("background dependency install failed", { dir, error: String(exit.cause) })
: Effect.void,
),
Effect.asVoid,
Effect.forkDetach,
)
deps.push(dep)
if (Flag.OPENCODE_PURE) {
yield* Effect.logDebug("skipping background dependency install in pure mode", { dir })
} else {
const dep = yield* npmSvc
.install(dir, {
add: [
{
name: "@opencode-ai/plugin",
version: InstallationLocal ? undefined : InstallationVersion,
},
],
})
.pipe(
Effect.exit,
Effect.tap((exit) =>
Exit.isFailure(exit)
? Effect.logWarning("background dependency install failed", { dir, error: String(exit.cause) })
: Effect.void,
),
Effect.asVoid,
Effect.forkDetach,
)
deps.push(dep)
}

result.command = mergeDeep(result.command ?? {}, yield* Effect.promise(() => ConfigCommand.load(dir)))
result.agent = mergeDeep(result.agent ?? {}, yield* Effect.promise(() => ConfigAgent.load(dir)))
Expand Down
27 changes: 24 additions & 3 deletions packages/opencode/src/session/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,13 +294,33 @@ const live: Layer.Layer<
// Copilot returns the authoritative billed amount only in provider-specific response fields.
includeRawChunks: input.model.providerID.includes("github-copilot"),
async experimental_repairToolCall(failed) {
const lower = failed.toolCall.toolName.toLowerCase()
if (lower !== failed.toolCall.toolName && prepared.tools[lower]) {
const name = failed.toolCall.toolName
const lower = name.toLowerCase()
if (lower !== name && prepared.tools[lower]) {
return {
...failed.toolCall,
toolName: lower,
}
}
// Normalize common local-LLM / MCP naming drift before giving up.
const normalized = name
.replaceAll("findevil_mcp_", "findevil-mcp_")
.replaceAll("findevil_agent_mcp_", "findevil-agent-mcp_")
.replaceAll("findevil-mcp-agent-mcp_", "findevil-agent-mcp_")
if (normalized !== name && prepared.tools[normalized]) {
return {
...failed.toolCall,
toolName: normalized,
}
}
if (prepared.tools[lower.replaceAll("_", "-")]) {
return {
...failed.toolCall,
toolName: lower.replaceAll("_", "-"),
}
}
// Keep toolName "invalid" so InvalidTool can return a structured error.
// activeTools MUST include "invalid" or the SDK reports "unavailable tool 'invalid'".
return {
...failed.toolCall,
input: JSON.stringify({
Expand All @@ -314,7 +334,8 @@ const live: Layer.Layer<
topP: prepared.params.topP,
topK: prepared.params.topK,
providerOptions: ProviderTransform.providerOptions(input.model, prepared.params.options),
activeTools: Object.keys(prepared.tools).filter((x) => x !== "invalid"),
// Include "invalid" so experimental_repairToolCall can execute; description says do not use.
activeTools: Object.keys(prepared.tools),
tools: prepared.tools,
toolChoice: input.toolChoice,
maxOutputTokens: prepared.params.maxOutputTokens,
Expand Down
8 changes: 6 additions & 2 deletions packages/opencode/src/tool/invalid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ export const Parameters = Schema.Struct({
export const InvalidTool = Tool.define(
"invalid",
Effect.succeed({
description: "Do not use",
description:
"Internal repair only — models must never choose this tool. Use exact findevil-mcp_* / findevil-agent-mcp_* names.",
parameters: Parameters,
execute: (params: { tool: string; error: string }) =>
Effect.succeed({
title: "Invalid Tool",
output: `The arguments provided to the tool are invalid: ${params.error}`,
output:
`Tool call failed for '${params.tool}': ${params.error}. ` +
`Retry with an exact available tool name (findevil-mcp_* or findevil-agent-mcp_* only). ` +
`Do not call a tool named invalid.`,
metadata: {},
}),
}),
Expand Down
Loading
Loading