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
5 changes: 5 additions & 0 deletions .changeset/add-opencode-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"task-master-ai": minor
---

Add OpenCode as an AI provider. Delegates authentication, model routing, and provider selection to a running OpenCode server via `ai-sdk-provider-opencode-sdk`, so Task Master stays agnostic about the underlying LLM. This unblocks enterprise users whose organisations permit OpenCode (including its GitHub Copilot backend) but prohibit direct LLM API credentials.
36 changes: 32 additions & 4 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"ai-sdk-provider-claude-code": "^2.2.4",
"ai-sdk-provider-codex-cli": "^0.7.0",
"ai-sdk-provider-gemini-cli": "^1.4.0",
"ai-sdk-provider-opencode-sdk": "^0.0.3",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"boxen": "^8.0.1",
Expand Down
3 changes: 2 additions & 1 deletion packages/tm-core/src/common/constants/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export const CUSTOM_PROVIDERS = {
MCP: 'mcp',
GEMINI_CLI: 'gemini-cli',
GROK_CLI: 'grok-cli',
CODEX_CLI: 'codex-cli'
CODEX_CLI: 'codex-cli',
OPENCODE: 'opencode'
} as const;

export type CustomProvider =
Expand Down
4 changes: 3 additions & 1 deletion scripts/modules/ai-services-unified.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
OllamaAIProvider,
OpenAICompatibleProvider,
OpenAIProvider,
OpencodeProvider,
OpenRouterAIProvider,
PerplexityAIProvider,
VertexAIProvider,
Expand Down Expand Up @@ -84,7 +85,8 @@ const PROVIDERS = {
'claude-code': new ClaudeCodeProvider(),
'codex-cli': new CodexCliProvider(),
'gemini-cli': new GeminiCliProvider(),
'grok-cli': new GrokCliProvider()
'grok-cli': new GrokCliProvider(),
opencode: new OpencodeProvider()
};

function _getProvider(providerName) {
Expand Down
44 changes: 28 additions & 16 deletions scripts/modules/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -3643,6 +3643,10 @@ ${result.result}
'--codex-cli',
'Allow setting a Codex CLI model ID (use with --set-*)'
)
.option(
'--opencode',
'Allow setting an OpenCode model ID (use with --set-*)'
)
.option(
'--lmstudio',
'Allow setting a custom LM Studio model ID (use with --set-*)'
Expand Down Expand Up @@ -3671,6 +3675,7 @@ Examples:
$ task-master models --set-main claude-3-5-sonnet@20241022 --vertex # Set custom Vertex AI model for main role
$ task-master models --set-main gemini-2.5-pro --gemini-cli # Set Gemini CLI model for main role
$ task-master models --set-main gpt-5-codex --codex-cli # Set Codex CLI model for main role
$ task-master models --set-main anthropic/claude-sonnet-4-5 --opencode # Set OpenCode model for main role
$ task-master models --set-main qwen3-vl-4b --lmstudio # Set LM Studio model for main role (defaults to http://localhost:1234/v1)
$ task-master models --set-main qwen3-vl-4b --lmstudio --baseURL http://localhost:8000/v1 # Set LM Studio model with custom base URL
$ task-master models --set-main my-model --openai-compatible --baseURL http://localhost:8000/v1 # Set custom OpenAI-compatible model with custom endpoint
Expand All @@ -3692,13 +3697,14 @@ Examples:
options.claudeCode,
options.geminiCli,
options.codexCli,
options.opencode,
options.lmstudio,
options.openaiCompatible
].filter(Boolean).length;
if (providerFlags > 1) {
console.error(
chalk.red(
'Error: Cannot use multiple provider flags (--openrouter, --ollama, --bedrock, --claude-code, --gemini-cli, --codex-cli, --lmstudio, --openai-compatible) simultaneously.'
'Error: Cannot use multiple provider flags (--openrouter, --ollama, --bedrock, --claude-code, --gemini-cli, --codex-cli, --opencode, --lmstudio, --openai-compatible) simultaneously.'
)
);
process.exit(1);
Expand Down Expand Up @@ -3746,11 +3752,13 @@ Examples:
? 'gemini-cli'
: options.codexCli
? 'codex-cli'
: options.lmstudio
? 'lmstudio'
: options.openaiCompatible
? 'openai-compatible'
: undefined,
: options.opencode
? 'opencode'
: options.lmstudio
? 'lmstudio'
: options.openaiCompatible
? 'openai-compatible'
: undefined,
baseURL: options.baseURL
});
if (result.success) {
Expand Down Expand Up @@ -3779,11 +3787,13 @@ Examples:
? 'gemini-cli'
: options.codexCli
? 'codex-cli'
: options.lmstudio
? 'lmstudio'
: options.openaiCompatible
? 'openai-compatible'
: undefined,
: options.opencode
? 'opencode'
: options.lmstudio
? 'lmstudio'
: options.openaiCompatible
? 'openai-compatible'
: undefined,
baseURL: options.baseURL
});
if (result.success) {
Expand Down Expand Up @@ -3814,11 +3824,13 @@ Examples:
? 'gemini-cli'
: options.codexCli
? 'codex-cli'
: options.lmstudio
? 'lmstudio'
: options.openaiCompatible
? 'openai-compatible'
: undefined,
: options.opencode
? 'opencode'
: options.lmstudio
? 'lmstudio'
: options.openaiCompatible
? 'openai-compatible'
: undefined,
baseURL: options.baseURL
});
if (result.success) {
Expand Down
48 changes: 45 additions & 3 deletions scripts/modules/config-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const DEFAULTS = {
},
claudeCode: {},
codexCli: {},
opencode: {},
grokCli: {
Comment thread
bjcoombs marked this conversation as resolved.
timeout: 120000,
workingDirectory: null,
Expand Down Expand Up @@ -161,6 +162,7 @@ function _loadAndValidateConfig(explicitRoot = null, options = {}) {
global: { ...defaults.global, ...parsedConfig?.global },
claudeCode: { ...defaults.claudeCode, ...parsedConfig?.claudeCode },
codexCli: { ...defaults.codexCli, ...parsedConfig?.codexCli },
opencode: { ...defaults.opencode, ...parsedConfig?.opencode },
grokCli: { ...defaults.grokCli, ...parsedConfig?.grokCli }
};
configSource = `file (${configPath})`; // Update source info
Expand Down Expand Up @@ -504,6 +506,23 @@ function getClaudeCodeSettingsForCommand(
return { ...settings, ...commandSpecific[commandName] };
}

// --- Opencode Settings Getters ---

function getOpencodeSettings(explicitRoot = null, forceReload = false) {
const config = getConfig(explicitRoot, forceReload);
return { ...DEFAULTS.opencode, ...(config?.opencode || {}) };
}

function getOpencodeSettingsForCommand(
commandName,
explicitRoot = null,
forceReload = false
) {
const settings = getOpencodeSettings(explicitRoot, forceReload);
const commandSpecific = settings?.commandSpecific || {};
return { ...settings, ...commandSpecific[commandName] };
}

Comment thread
bjcoombs marked this conversation as resolved.
Comment thread
bjcoombs marked this conversation as resolved.
function getGrokCliSettings(explicitRoot = null, forceReload = false) {
const config = getConfig(explicitRoot, forceReload);
// Ensure Grok CLI defaults are applied if Grok CLI section is missing
Expand Down Expand Up @@ -612,7 +631,8 @@ function hasCodebaseAnalysis(
currentProvider === CUSTOM_PROVIDERS.CLAUDE_CODE ||
currentProvider === CUSTOM_PROVIDERS.GEMINI_CLI ||
currentProvider === CUSTOM_PROVIDERS.GROK_CLI ||
currentProvider === CUSTOM_PROVIDERS.CODEX_CLI
currentProvider === CUSTOM_PROVIDERS.CODEX_CLI ||
currentProvider === CUSTOM_PROVIDERS.OPENCODE
);
}

Expand Down Expand Up @@ -875,7 +895,8 @@ function isApiKeySet(providerName, session = null, projectRoot = null) {
CUSTOM_PROVIDERS.GEMINI_CLI,
CUSTOM_PROVIDERS.GROK_CLI,
CUSTOM_PROVIDERS.MCP,
CUSTOM_PROVIDERS.CODEX_CLI
CUSTOM_PROVIDERS.CODEX_CLI,
CUSTOM_PROVIDERS.OPENCODE
];

if (providersWithoutApiKeys.includes(providerName?.toLowerCase())) {
Expand Down Expand Up @@ -936,6 +957,23 @@ function isApiKeySet(providerName, session = null, projectRoot = null) {
* @returns {boolean} True if the key exists and is not a placeholder, false otherwise.
*/
function getMcpApiKeyStatus(providerName, projectRoot = null) {
// Short-circuit for providers that do not require an API key — these
// should not hinge on the presence of .cursor/mcp.json or a detectable
// project root.
const provider = providerName?.toLowerCase();
if (
provider === CUSTOM_PROVIDERS.OLLAMA ||
provider === CUSTOM_PROVIDERS.BEDROCK ||
provider === CUSTOM_PROVIDERS.CLAUDE_CODE ||
provider === CUSTOM_PROVIDERS.CODEX_CLI ||
provider === CUSTOM_PROVIDERS.GEMINI_CLI ||
provider === CUSTOM_PROVIDERS.GROK_CLI ||
provider === CUSTOM_PROVIDERS.OPENCODE ||
provider === CUSTOM_PROVIDERS.MCP
) {
return true;
}

const rootDir = projectRoot || findProjectRoot(); // Use existing root finding
if (!rootDir) {
console.warn(
Expand Down Expand Up @@ -1246,7 +1284,8 @@ export const providersWithoutApiKeys = [
CUSTOM_PROVIDERS.GEMINI_CLI,
CUSTOM_PROVIDERS.GROK_CLI,
CUSTOM_PROVIDERS.MCP,
CUSTOM_PROVIDERS.CODEX_CLI
CUSTOM_PROVIDERS.CODEX_CLI,
CUSTOM_PROVIDERS.OPENCODE
];

export {
Expand All @@ -1261,6 +1300,9 @@ export {
// Codex CLI settings
getCodexCliSettings,
getCodexCliSettingsForCommand,
// Opencode settings
getOpencodeSettings,
getOpencodeSettingsForCommand,
// Grok CLI settings
getGrokCliSettings,
getGrokCliSettingsForCommand,
Expand Down
Loading
Loading