cortexweaver
Version:
CortexWeaver is a command-line interface (CLI) tool that orchestrates a swarm of specialized AI agents, powered by Claude Code and Gemini CLI, to assist in software development. It transforms a high-level project plan (plan.md) into a series of coordinate
247 lines • 9.88 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClaudeClient = exports.ClaudeModel = void 0;
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
var ClaudeModel;
(function (ClaudeModel) {
ClaudeModel["OPUS"] = "claude-3-opus-20240229";
ClaudeModel["SONNET"] = "claude-3-sonnet-20240229";
ClaudeModel["HAIKU"] = "claude-3-haiku-20240307";
})(ClaudeModel || (exports.ClaudeModel = ClaudeModel = {}));
// Pricing per 1M tokens (as of 2024)
const MODEL_PRICING = {
[ClaudeModel.OPUS]: { input: 15.00, output: 75.00 },
[ClaudeModel.SONNET]: { input: 3.00, output: 15.00 },
[ClaudeModel.HAIKU]: { input: 0.25, output: 1.25 }
};
class ClaudeClient {
constructor(config) {
if (!config.apiKey && !config.sessionToken) {
throw new Error('Either API key or session token is required');
}
this.config = {
apiKey: config.apiKey,
sessionToken: config.sessionToken,
defaultModel: config.defaultModel || ClaudeModel.SONNET,
maxTokens: config.maxTokens || 4096,
temperature: config.temperature || 0.7,
budgetLimit: config.budgetLimit || Infinity,
budgetWarningThreshold: config.budgetWarningThreshold || 0.8
};
// Use session token if available, otherwise use API key
if (config.sessionToken) {
if (config.sessionToken === 'claude-code-inherited') {
// We're running in Claude Code environment, use the inherited authentication
this.anthropic = this.createClaudeCodeInheritedClient();
}
else {
// For manual session tokens, use custom client
this.anthropic = this.createSessionAwareAnthropicClient(config.sessionToken);
}
}
else {
this.anthropic = new sdk_1.default({
apiKey: this.config.apiKey
});
}
this.tokenUsage = {
totalInputTokens: 0,
totalOutputTokens: 0,
totalTokens: 0,
requestCount: 0,
estimatedCost: 0
};
}
getConfiguration() {
return { ...this.config };
}
updateConfiguration(updates) {
if (updates.temperature !== undefined) {
if (updates.temperature < 0 || updates.temperature > 1) {
throw new Error('Temperature must be between 0 and 1');
}
}
if (updates.maxTokens !== undefined) {
if (updates.maxTokens <= 0) {
throw new Error('Max tokens must be positive');
}
}
Object.assign(this.config, updates);
}
async sendMessage(message, options = {}) {
await this.checkBudgetLimit();
const model = options.model || this.config.defaultModel;
const maxTokens = options.maxTokens || this.config.maxTokens;
const temperature = options.temperature || this.config.temperature;
const maxRetries = options.maxRetries || 0;
const retryDelay = options.retryDelay || 1000;
const messages = [
...(options.conversationHistory || []),
{ role: 'user', content: message }
];
const requestPayload = {
model,
max_tokens: maxTokens,
temperature,
messages
};
if (options.systemPrompt) {
requestPayload.system = options.systemPrompt;
}
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await this.anthropic.messages.create(requestPayload);
if (!response || !response.content || !Array.isArray(response.content)) {
throw new Error('Invalid response format');
}
const textContent = response.content
.filter(block => 'text' in block)
.map(block => block.text)
.join('');
const tokenUsage = {
inputTokens: response.usage?.input_tokens || 0,
outputTokens: response.usage?.output_tokens || 0,
totalTokens: (response.usage?.input_tokens || 0) + (response.usage?.output_tokens || 0)
};
this.updateTokenUsage(tokenUsage, model);
return {
content: textContent,
tokenUsage,
model: response.model
};
}
catch (error) {
lastError = error;
if (attempt < maxRetries && this.isRetryableError(error)) {
await this.delay(retryDelay);
continue;
}
throw error;
}
}
throw lastError;
}
async sendMessageStream(message, options = {}) {
await this.checkBudgetLimit();
const model = options.model || this.config.defaultModel;
const maxTokens = options.maxTokens || this.config.maxTokens;
const temperature = options.temperature || this.config.temperature;
const messages = [
...(options.conversationHistory || []),
{ role: 'user', content: message }
];
const requestPayload = {
model,
max_tokens: maxTokens,
temperature,
messages
};
if (options.systemPrompt) {
requestPayload.system = options.systemPrompt;
}
const stream = await this.anthropic.messages.stream(requestPayload);
return {
[Symbol.asyncIterator]: async function* () {
for await (const chunk of stream) {
if (chunk.type === 'content_block_delta' && 'text' in chunk.delta) {
yield chunk.delta.text;
}
}
}
};
}
getTokenUsage() {
return { ...this.tokenUsage };
}
resetTokenUsage() {
this.tokenUsage = {
totalInputTokens: 0,
totalOutputTokens: 0,
totalTokens: 0,
requestCount: 0,
estimatedCost: 0
};
}
setDefaultModel(model) {
if (!Object.values(ClaudeModel).includes(model)) {
throw new Error('Invalid model');
}
this.config.defaultModel = model;
}
getAvailableModels() {
return Object.values(ClaudeModel);
}
updateTokenUsage(usage, model) {
this.tokenUsage.totalInputTokens += usage.inputTokens;
this.tokenUsage.totalOutputTokens += usage.outputTokens;
this.tokenUsage.totalTokens += usage.totalTokens;
this.tokenUsage.requestCount += 1;
// Calculate cost
const pricing = MODEL_PRICING[model];
const inputCost = (usage.inputTokens / 1000000) * pricing.input;
const outputCost = (usage.outputTokens / 1000000) * pricing.output;
this.tokenUsage.estimatedCost += inputCost + outputCost;
// Check budget warning
if (this.config.budgetLimit < Infinity) {
const budgetUsedRatio = this.tokenUsage.estimatedCost / this.config.budgetLimit;
if (budgetUsedRatio >= this.config.budgetWarningThreshold && budgetUsedRatio < 1) {
console.warn(`Budget warning: ${(budgetUsedRatio * 100).toFixed(1)}% of budget used`);
}
}
}
async checkBudgetLimit() {
if (this.config.budgetLimit < Infinity && this.tokenUsage.estimatedCost >= this.config.budgetLimit) {
throw new Error('Budget limit exceeded');
}
}
isRetryableError(error) {
const errorMessage = error?.message?.toLowerCase() || '';
return errorMessage.includes('rate limit') ||
errorMessage.includes('timeout') ||
errorMessage.includes('server error');
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Create an Anthropic client that inherits Claude Code's authentication
*/
createClaudeCodeInheritedClient() {
// When running in Claude Code environment, create a placeholder client
// In a real implementation, this would need to integrate with Claude Code's authentication
// For now, this signals that session authentication is available
return new sdk_1.default({
apiKey: 'sk-ant-api03-placeholder' // Placeholder that follows Anthropic's API key format
});
}
/**
* Create a session-aware Anthropic client that uses Claude Code session tokens
*/
createSessionAwareAnthropicClient(sessionToken) {
// Create a custom fetch function that adds session token authentication
const customFetch = async (url, options) => {
const headers = new Headers(options?.headers);
// Add Claude Code session authentication
headers.set('Cookie', `sessionKey=${sessionToken}`);
headers.set('Content-Type', 'application/json');
// Remove any existing Authorization header since we're using session cookies
headers.delete('Authorization');
const modifiedOptions = {
...options,
headers
};
return fetch(url, modifiedOptions);
};
return new sdk_1.default({
apiKey: 'session-token-placeholder', // Required by SDK but not used
fetch: customFetch,
baseURL: 'https://api.anthropic.com'
});
}
}
exports.ClaudeClient = ClaudeClient;
//# sourceMappingURL=claude-client.js.map