feat: add Claude Code provider support
Implements Claude Code as a new AI provider that uses the Claude Code CLI without requiring API keys. This enables users to leverage Claude models through their local Claude Code installation. Key changes: - Add complete AI SDK v1 implementation for Claude Code provider - Custom SDK with streaming/non-streaming support - Session management for conversation continuity - JSON extraction for object generation mode - Support for advanced settings (maxTurns, allowedTools, etc.) - Integrate Claude Code into Task Master's provider system - Update ai-services-unified.js to handle keyless authentication - Add provider to supported-models.json with opus/sonnet models - Ensure correct maxTokens values are applied (opus: 32000, sonnet: 64000) - Fix maxTokens configuration issue - Add max_tokens property to getAvailableModels() output - Update setModel() to properly handle claude-code models - Create update-config-tokens.js utility for init process - Add comprehensive documentation - User guide with configuration examples - Advanced settings explanation and future integration options The implementation maintains full backward compatibility with existing providers while adding seamless Claude Code support to all Task Master commands.
This commit is contained in:
committed by
Ralph Khreish
parent
1b8c320c57
commit
3e838ed34b
@@ -28,6 +28,7 @@ import {
|
||||
convertAllRulesToProfileRules,
|
||||
getRulesProfile
|
||||
} from '../src/utils/rule-transformer.js';
|
||||
import { updateConfigMaxTokens } from './modules/update-config-tokens.js';
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import {
|
||||
@@ -506,6 +507,14 @@ function createProjectStructure(
|
||||
...replacements
|
||||
}
|
||||
);
|
||||
|
||||
// Update config.json with correct maxTokens values from supported-models.json
|
||||
const configPath = path.join(targetDir, TASKMASTER_CONFIG_FILE);
|
||||
if (updateConfigMaxTokens(configPath)) {
|
||||
log('info', 'Updated config with correct maxTokens values');
|
||||
} else {
|
||||
log('warn', 'Could not update maxTokens in config');
|
||||
}
|
||||
|
||||
// Copy .gitignore
|
||||
copyTemplateFile('gitignore', path.join(targetDir, GITIGNORE_FILE));
|
||||
|
||||
@@ -44,7 +44,8 @@ import {
|
||||
OllamaAIProvider,
|
||||
BedrockAIProvider,
|
||||
AzureProvider,
|
||||
VertexAIProvider
|
||||
VertexAIProvider,
|
||||
ClaudeCodeProvider
|
||||
} from '../../src/ai-providers/index.js';
|
||||
|
||||
// Create provider instances
|
||||
@@ -58,7 +59,8 @@ const PROVIDERS = {
|
||||
ollama: new OllamaAIProvider(),
|
||||
bedrock: new BedrockAIProvider(),
|
||||
azure: new AzureProvider(),
|
||||
vertex: new VertexAIProvider()
|
||||
vertex: new VertexAIProvider(),
|
||||
'claude-code': new ClaudeCodeProvider()
|
||||
};
|
||||
|
||||
// Helper function to get cost for a specific model
|
||||
@@ -225,6 +227,11 @@ function _extractErrorMessage(error) {
|
||||
* @throws {Error} If a required API key is missing.
|
||||
*/
|
||||
function _resolveApiKey(providerName, session, projectRoot = null) {
|
||||
// Claude Code doesn't require an API key
|
||||
if (providerName === 'claude-code') {
|
||||
return 'claude-code-no-key-required';
|
||||
}
|
||||
|
||||
const keyMap = {
|
||||
openai: 'OPENAI_API_KEY',
|
||||
anthropic: 'ANTHROPIC_API_KEY',
|
||||
@@ -236,7 +243,8 @@ function _resolveApiKey(providerName, session, projectRoot = null) {
|
||||
xai: 'XAI_API_KEY',
|
||||
ollama: 'OLLAMA_API_KEY',
|
||||
bedrock: 'AWS_ACCESS_KEY_ID',
|
||||
vertex: 'GOOGLE_API_KEY'
|
||||
vertex: 'GOOGLE_API_KEY',
|
||||
'claude-code': 'CLAUDE_CODE_API_KEY' // Not actually used, but included for consistency
|
||||
};
|
||||
|
||||
const envVarName = keyMap[providerName];
|
||||
|
||||
@@ -507,6 +507,11 @@ function isApiKeySet(providerName, session = null, projectRoot = null) {
|
||||
return true; // Indicate key status is effectively "OK"
|
||||
}
|
||||
|
||||
// Claude Code doesn't require an API key
|
||||
if (providerName?.toLowerCase() === 'claude-code') {
|
||||
return true; // No API key needed
|
||||
}
|
||||
|
||||
const keyMap = {
|
||||
openai: 'OPENAI_API_KEY',
|
||||
anthropic: 'ANTHROPIC_API_KEY',
|
||||
@@ -517,6 +522,7 @@ function isApiKeySet(providerName, session = null, projectRoot = null) {
|
||||
openrouter: 'OPENROUTER_API_KEY',
|
||||
xai: 'XAI_API_KEY',
|
||||
vertex: 'GOOGLE_API_KEY', // Vertex uses the same key as Google
|
||||
'claude-code': 'CLAUDE_CODE_API_KEY', // Not actually used, but included for consistency
|
||||
bedrock: 'AWS_ACCESS_KEY_ID' // Bedrock uses AWS credentials
|
||||
// Add other providers as needed
|
||||
};
|
||||
@@ -601,6 +607,8 @@ function getMcpApiKeyStatus(providerName, projectRoot = null) {
|
||||
break;
|
||||
case 'ollama':
|
||||
return true; // No key needed
|
||||
case 'claude-code':
|
||||
return true; // No key needed
|
||||
case 'mistral':
|
||||
apiKeyToCheck = mcpEnv.MISTRAL_API_KEY;
|
||||
placeholderValue = 'YOUR_MISTRAL_API_KEY_HERE';
|
||||
@@ -664,7 +672,8 @@ function getAvailableModels() {
|
||||
provider: provider,
|
||||
swe_score: sweScore,
|
||||
cost_per_1m_tokens: cost,
|
||||
allowed_roles: allowedRoles
|
||||
allowed_roles: allowedRoles,
|
||||
max_tokens: modelObj.max_tokens
|
||||
});
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -610,5 +610,21 @@
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 32768
|
||||
}
|
||||
],
|
||||
"claude-code": [
|
||||
{
|
||||
"id": "opus",
|
||||
"swe_score": 0.725,
|
||||
"cost_per_1m_tokens": { "input": 0, "output": 0 },
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"max_tokens": 32000
|
||||
},
|
||||
{
|
||||
"id": "sonnet",
|
||||
"swe_score": 0.727,
|
||||
"cost_per_1m_tokens": { "input": 0, "output": 0 },
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"max_tokens": 64000
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -425,7 +425,7 @@ async function setModel(role, modelId, options = {}) {
|
||||
let warningMessage = null;
|
||||
|
||||
// Find the model data in internal list initially to see if it exists at all
|
||||
const modelData = availableModels.find((m) => m.id === modelId);
|
||||
let modelData = availableModels.find((m) => m.id === modelId);
|
||||
|
||||
// --- Revised Logic: Prioritize providerHint --- //
|
||||
|
||||
@@ -495,6 +495,24 @@ async function setModel(role, modelId, options = {}) {
|
||||
determinedProvider = CUSTOM_PROVIDERS.BEDROCK;
|
||||
warningMessage = `Warning: Custom Bedrock model '${modelId}' set. Please ensure the model ID is valid and accessible in your AWS account.`;
|
||||
report('warn', warningMessage);
|
||||
} else if (providerHint === CUSTOM_PROVIDERS.CLAUDE_CODE) {
|
||||
// Claude Code provider - check if model exists in our list
|
||||
determinedProvider = CUSTOM_PROVIDERS.CLAUDE_CODE;
|
||||
// Re-find modelData specifically for claude-code provider
|
||||
const claudeCodeModels = availableModels.filter(
|
||||
(m) => m.provider === 'claude-code'
|
||||
);
|
||||
const claudeCodeModelData = claudeCodeModels.find(
|
||||
(m) => m.id === modelId
|
||||
);
|
||||
if (claudeCodeModelData) {
|
||||
// Update modelData to the found claude-code model
|
||||
modelData = claudeCodeModelData;
|
||||
report('info', `Setting Claude Code model '${modelId}'.`);
|
||||
} else {
|
||||
warningMessage = `Warning: Claude Code model '${modelId}' not found in supported models. Setting without validation.`;
|
||||
report('warn', warningMessage);
|
||||
}
|
||||
} else if (providerHint === CUSTOM_PROVIDERS.AZURE) {
|
||||
// Set provider without model validation since Azure models are managed by Azure
|
||||
determinedProvider = CUSTOM_PROVIDERS.AZURE;
|
||||
@@ -547,11 +565,16 @@ async function setModel(role, modelId, options = {}) {
|
||||
|
||||
// Update configuration
|
||||
currentConfig.models[role] = {
|
||||
...currentConfig.models[role], // Keep existing params like maxTokens
|
||||
...currentConfig.models[role], // Keep existing params like temperature
|
||||
provider: determinedProvider,
|
||||
modelId: modelId
|
||||
};
|
||||
|
||||
// If model data is available, update maxTokens from supported-models.json
|
||||
if (modelData && modelData.max_tokens) {
|
||||
currentConfig.models[role].maxTokens = modelData.max_tokens;
|
||||
}
|
||||
|
||||
// Write updated configuration
|
||||
const writeResult = writeConfig(currentConfig, projectRoot);
|
||||
if (!writeResult) {
|
||||
|
||||
53
scripts/modules/update-config-tokens.js
Normal file
53
scripts/modules/update-config-tokens.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* update-config-tokens.js
|
||||
* Updates config.json with correct maxTokens values from supported-models.json
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
/**
|
||||
* Updates the config file with correct maxTokens values from supported-models.json
|
||||
* @param {string} configPath - Path to the config.json file to update
|
||||
* @returns {boolean} True if successful, false otherwise
|
||||
*/
|
||||
export function updateConfigMaxTokens(configPath) {
|
||||
try {
|
||||
// Load supported models
|
||||
const supportedModelsPath = path.join(__dirname, 'supported-models.json');
|
||||
const supportedModels = JSON.parse(fs.readFileSync(supportedModelsPath, 'utf-8'));
|
||||
|
||||
// Load config
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
||||
|
||||
// Update each role's maxTokens if the model exists in supported-models.json
|
||||
const roles = ['main', 'research', 'fallback'];
|
||||
|
||||
for (const role of roles) {
|
||||
if (config.models && config.models[role]) {
|
||||
const provider = config.models[role].provider;
|
||||
const modelId = config.models[role].modelId;
|
||||
|
||||
// Find the model in supported models
|
||||
if (supportedModels[provider]) {
|
||||
const modelData = supportedModels[provider].find(m => m.id === modelId);
|
||||
if (modelData && modelData.max_tokens) {
|
||||
config.models[role].maxTokens = modelData.max_tokens;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write back the updated config
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error updating config maxTokens:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user