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 src/agent/AgentBackend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export interface McpServerConfig {
export type AgentTransport = 'native-claude' | 'mcp-codex' | 'acp';

/** Agent identifier */
export type AgentId = 'claude' | 'codex' | 'gemini' | 'opencode' | 'claude-acp' | 'codex-acp';
export type AgentId = 'claude' | 'codex' | 'gemini' | 'opencode' | 'claude-acp' | 'codex-acp' | 'codebuddy' | 'codebuddy-acp';

/**
* Configuration for creating an agent backend
Expand Down
130 changes: 130 additions & 0 deletions src/agent/acp/codebuddy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* CodeBuddy ACP Backend - CodeBuddy Code agent via ACP
*
* This module provides a factory function for creating a CodeBuddy backend
* that communicates using the Agent Client Protocol (ACP).
*
* CodeBuddy Code supports ACP for external tool integration and communication.
*/

import { AcpSdkBackend, type AcpSdkBackendOptions, type AcpPermissionHandler } from './AcpSdkBackend';
import type { AgentBackend, McpServerConfig } from '../AgentBackend';
import { agentRegistry, type AgentFactoryOptions } from '../AgentRegistry';
import { logger } from '@/ui/logger';
import {
CODEBUDDY_API_KEY_ENV,
CODEBUDDY_MODEL_ENV,
DEFAULT_CODEBUDDY_MODEL,
CODEBUDDY_CLI_COMMAND
} from '@/codebuddy/constants';
import {
readCodebuddyLocalConfig,
determineCodebuddyModel,
getCodebuddyModelSource
} from '@/codebuddy/utils/config';

/**
* Options for creating a CodeBuddy ACP backend
*/
export interface CodebuddyBackendOptions extends AgentFactoryOptions {
/** API key for CodeBuddy (defaults to CODEBUDDY_API_KEY env var) */
apiKey?: string;

/** OAuth token from Happy cloud - highest priority */
cloudToken?: string;

/** Model to use. If undefined, will use local config, env var, or default.
* If explicitly set to null, will use default (skip local config).
* (defaults to CODEBUDDY_MODEL env var or 'claude-sonnet-4-20250514') */
model?: string | null;

/** MCP servers to make available to the agent */
mcpServers?: Record<string, McpServerConfig>;

/** Optional permission handler for tool approval */
permissionHandler?: AcpPermissionHandler;
}

/**
* Create a CodeBuddy backend using ACP (official SDK).
*
* The CodeBuddy CLI must be installed and available in PATH.
* Uses ACP mode for communication.
*
* @param options - Configuration options
* @returns AgentBackend instance for CodeBuddy
*/
export function createCodebuddyBackend(options: CodebuddyBackendOptions): AgentBackend {

// Resolve API key from multiple sources (in priority order):
// 1. Happy cloud OAuth token
// 2. Local CodeBuddy config files (~/.codebuddy/)
// 3. CODEBUDDY_API_KEY environment variable
// 4. Explicit apiKey option

// Try reading from local CodeBuddy config (token and model)
const localConfig = readCodebuddyLocalConfig();

let apiKey = options.cloudToken // 1. Happy cloud token (passed from runCodebuddy)
|| localConfig.token // 2. Local config (~/.codebuddy/)
|| process.env[CODEBUDDY_API_KEY_ENV] // 3. CODEBUDDY_API_KEY env var
|| options.apiKey; // 4. Explicit apiKey option (fallback)

if (!apiKey) {
logger.warn(`[CodeBuddy] No API key found. Set ${CODEBUDDY_API_KEY_ENV} environment variable or configure authentication.`);
}

// Command to run codebuddy
const codebuddyCommand = CODEBUDDY_CLI_COMMAND;

// Get model from options, local config, system environment, or use default
const model = determineCodebuddyModel(options.model, localConfig);

// Build args - use ACP mode flag
// Note: The actual flag might need to be adjusted based on CodeBuddy CLI implementation
const codebuddyArgs = ['--acp'];

const backendOptions: AcpSdkBackendOptions = {
agentName: 'codebuddy',
cwd: options.cwd,
command: codebuddyCommand,
args: codebuddyArgs,
env: {
...options.env,
...(apiKey ? { [CODEBUDDY_API_KEY_ENV]: apiKey } : {}),
// Pass model via env var
[CODEBUDDY_MODEL_ENV]: model,
// Suppress debug output to avoid stdout pollution
NODE_ENV: 'production',
DEBUG: '',
},
mcpServers: options.mcpServers,
permissionHandler: options.permissionHandler,
};

// Determine model source for logging
const modelSource = getCodebuddyModelSource(options.model, localConfig);

logger.debug('[CodeBuddy] Creating ACP SDK backend with options:', {
cwd: backendOptions.cwd,
command: backendOptions.command,
args: backendOptions.args,
hasApiKey: !!apiKey,
model: model,
modelSource: modelSource,
mcpServerCount: options.mcpServers ? Object.keys(options.mcpServers).length : 0,
});

return new AcpSdkBackend(backendOptions);
}

/**
* Register CodeBuddy backend with the global agent registry.
*
* This function should be called during application initialization
* to make the CodeBuddy agent available for use.
*/
export function registerCodebuddyAgent(): void {
agentRegistry.register('codebuddy', (opts) => createCodebuddyBackend(opts));
logger.debug('[CodeBuddy] Registered with agent registry');
}
1 change: 1 addition & 0 deletions src/agent/acp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@

export { AcpSdkBackend, type AcpSdkBackendOptions } from './AcpSdkBackend';
export { createGeminiBackend, registerGeminiAgent, type GeminiBackendOptions } from './gemini';
export { createCodebuddyBackend, registerCodebuddyAgent, type CodebuddyBackendOptions } from './codebuddy';

2 changes: 1 addition & 1 deletion src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ export class ApiClient {
* Get vendor API token from the server
* Returns the token if it exists, null otherwise
*/
async getVendorToken(vendor: 'openai' | 'anthropic' | 'gemini'): Promise<any | null> {
async getVendorToken(vendor: 'openai' | 'anthropic' | 'gemini' | 'codebuddy'): Promise<any | null> {
try {
const response = await axios.get(
`${configuration.serverUrl}/v1/connect/${vendor}/token`,
Expand Down
67 changes: 67 additions & 0 deletions src/codebuddy/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* CodeBuddy Constants
*
* Centralized constants for CodeBuddy Code integration including environment variable names,
* default values, and directory/file path patterns.
*/

import { trimIdent } from '@/utils/trimIdent';

/** Environment variable name for CodeBuddy API key */
export const CODEBUDDY_API_KEY_ENV = 'CODEBUDDY_API_KEY';

/** Environment variable name for CodeBuddy model selection */
export const CODEBUDDY_MODEL_ENV = 'CODEBUDDY_MODEL';

/** Default CodeBuddy model */
export const DEFAULT_CODEBUDDY_MODEL = 'claude-sonnet-4-20250514';

/** CodeBuddy CLI command */
export const CODEBUDDY_CLI_COMMAND = 'codebuddy';

/**
* Directory names for CodeBuddy configuration
* Based on CodeBuddy Code documentation
*/
export const CODEBUDDY_DIR = '.codebuddy';

/** User-level CodeBuddy directory */
export const CODEBUDDY_USER_DIR = '~/.codebuddy';

/** Memory file name */
export const CODEBUDDY_MEMORY_FILE = 'CODEBUDDY.md';

/** Local memory file name (not committed to git) */
export const CODEBUDDY_LOCAL_MEMORY_FILE = 'CODEBUDDY.local.md';

/** Settings file name */
export const CODEBUDDY_SETTINGS_FILE = 'settings.json';

/** Local settings file name */
export const CODEBUDDY_LOCAL_SETTINGS_FILE = 'settings.local.json';

/** Rules directory name */
export const CODEBUDDY_RULES_DIR = 'rules';

/** Agents directory name */
export const CODEBUDDY_AGENTS_DIR = 'agents';

/**
* Instruction for changing chat title
* Used in system prompts to instruct agents to call change_title function
*/
export const CHANGE_TITLE_INSTRUCTION = trimIdent(
`Based on this message, call functions.happy__change_title to change chat session title that would represent the current task. If chat idea would change dramatically - call this function again to update the title.`
);

/**
* Available CodeBuddy models
*/
export const AVAILABLE_CODEBUDDY_MODELS = [
'claude-sonnet-4-20250514',
'claude-opus-4-20250514',
'claude-3-5-sonnet-20241022',
'claude-3-5-haiku-20241022',
] as const;

export type CodebuddyModel = typeof AVAILABLE_CODEBUDDY_MODELS[number];
77 changes: 77 additions & 0 deletions src/codebuddy/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* CodeBuddy Module
*
* Main entry point for CodeBuddy Code integration.
* Exports all public types, constants, and utilities.
*/

// Types
export type {
CodebuddyMode,
CodexMessagePayload,
CodebuddyLocalConfig,
CodebuddySettings,
CodebuddyMemory,
CodebuddyRule
} from './types';

// Constants
export {
CODEBUDDY_API_KEY_ENV,
CODEBUDDY_MODEL_ENV,
DEFAULT_CODEBUDDY_MODEL,
CODEBUDDY_CLI_COMMAND,
CODEBUDDY_DIR,
CODEBUDDY_USER_DIR,
CODEBUDDY_MEMORY_FILE,
CODEBUDDY_LOCAL_MEMORY_FILE,
CODEBUDDY_SETTINGS_FILE,
CODEBUDDY_LOCAL_SETTINGS_FILE,
CODEBUDDY_RULES_DIR,
CODEBUDDY_AGENTS_DIR,
CHANGE_TITLE_INSTRUCTION,
AVAILABLE_CODEBUDDY_MODELS,
type CodebuddyModel
} from './constants';

// Configuration utilities
export {
getUserCodebuddyDir,
getProjectCodebuddyDir,
readCodebuddyLocalConfig,
readCodebuddySettings,
readCodebuddyMemory,
readCodebuddyRules,
determineCodebuddyModel,
saveCodebuddyModelToConfig,
getInitialCodebuddyModel,
getCodebuddyModelSource,
buildSystemPrompt
} from './utils/config';

// Permission handler
export { CodebuddyPermissionHandler } from './utils/permissionHandler';
export type { PermissionResult, PendingRequest } from './utils/permissionHandler';

// Reasoning processor
export { CodebuddyReasoningProcessor } from './utils/reasoningProcessor';
export type {
ReasoningToolCall,
ReasoningToolResult,
ReasoningMessage,
ReasoningOutput
} from './utils/reasoningProcessor';

// Diff processor
export { CodebuddyDiffProcessor } from './utils/diffProcessor';
export type { DiffToolCall, DiffToolResult } from './utils/diffProcessor';

// Options parser
export {
hasIncompleteOptions,
parseOptionsFromText,
formatOptionsXml
} from './utils/optionsParser';

// Main entry point
export { runCodebuddy } from './runCodebuddy';
Loading