#!/usr/bin/env node // Claude Desktop — Bridge via Claude Agent SDK // // Nutzt @anthropic-ai/claude-agent-sdk (query-Funktion) // OAuth-Auth funktioniert automatisch (Claude Max Abo) // Kein CLI-Spawn, kein Overhead — direkte SDK-Aufrufe import { query } from '@anthropic-ai/claude-agent-sdk'; import { createInterface } from 'node:readline'; import { randomUUID } from 'node:crypto'; // Prozess am Leben halten const keepAlive = setInterval(() => {}, 60000); process.stdin.resume(); // ============ State ============ let activeAbort = null; let currentAgentId = null; let currentModel = process.env.CLAUDE_MODEL || 'opus'; // Verfügbare Modelle const AVAILABLE_MODELS = [ { id: 'haiku', name: 'Claude Haiku', description: 'Schnell & günstig' }, { id: 'sonnet', name: 'Claude Sonnet', description: 'Ausgewogen' }, { id: 'opus', name: 'Claude Opus', description: 'Leistungsstark' }, ]; // ============ Kommunikation mit Tauri ============ function sendToTauri(msg) { process.stdout.write(JSON.stringify(msg) + '\n'); } function sendEvent(event, payload = {}) { sendToTauri({ type: 'event', event, payload }); } function sendResponse(id, result) { sendToTauri({ type: 'response', id, result }); } function sendError(id, error) { sendToTauri({ type: 'response', id, error }); } // ============ Claude Agent SDK ============ async function sendMessage(message, requestId, model = null) { // Modell für diese Anfrage (Parameter > State > Default) const useModel = model || currentModel; currentAgentId = randomUUID(); activeAbort = new AbortController(); sendEvent('agent-started', { id: currentAgentId, type: 'Main', task: message.substring(0, 100), model: useModel, }); sendResponse(requestId, { agentId: currentAgentId, status: 'gestartet', model: useModel }); const startTime = Date.now(); let fullText = ''; let usedModel = useModel; try { const conversation = query({ prompt: message, options: { model: useModel, maxTurns: 25, abortController: activeAbort, }, }); for await (const event of conversation) { switch (event.type) { case 'assistant': // Text aus der Nachricht extrahieren if (event.message?.content) { for (const block of event.message.content) { if (block.type === 'text' && block.text) { fullText += block.text; sendEvent('text', { text: block.text }); } } } if (event.message?.model) { usedModel = event.message.model; } break; case 'tool_use': sendEvent('tool-start', { id: event.tool_use_id || randomUUID(), tool: event.name || 'unknown', input: event.input || {}, }); break; case 'tool_result': sendEvent('tool-end', { id: event.tool_use_id || '', success: !event.is_error, }); break; case 'result': // Endergebnis sendEvent('result', { text: fullText, cost: event.total_cost_usd || 0, tokens: { input: event.usage?.input_tokens || 0, output: event.usage?.output_tokens || 0, }, session_id: event.session_id || '', duration_ms: Date.now() - startTime, model: usedModel, }); break; default: // Andere Events still ignorieren break; } } } catch (err) { if (err.name === 'AbortError') { // Abgebrochen — kein Fehler } else { sendEvent('text', { text: `\n\n**Fehler:** ${err.message || err}` }); } } finally { sendEvent('agent-stopped', { id: currentAgentId, code: 0 }); sendEvent('all-stopped'); currentAgentId = null; activeAbort = null; } } // ============ Befehle von Tauri ============ function handleCommand(msg) { switch (msg.command) { case 'message': if (!msg.message) { sendError(msg.id, 'Keine Nachricht angegeben'); return; } // Modell kann pro Anfrage überschrieben werden sendMessage(msg.message, msg.id, msg.model); break; case 'stop': if (activeAbort) { activeAbort.abort(); } sendResponse(msg.id, { status: 'gestoppt' }); break; case 'set-model': if (!msg.model) { sendError(msg.id, 'Kein Modell angegeben'); return; } const validModels = AVAILABLE_MODELS.map(m => m.id); if (!validModels.includes(msg.model)) { sendError(msg.id, `Ungültiges Modell: ${msg.model}. Verfügbar: ${validModels.join(', ')}`); return; } currentModel = msg.model; sendResponse(msg.id, { model: currentModel, status: 'Modell geändert' }); sendEvent('model-changed', { model: currentModel }); break; case 'get-models': sendResponse(msg.id, { current: currentModel, available: AVAILABLE_MODELS, }); break; case 'status': sendResponse(msg.id, { model: currentModel, isProcessing: !!currentAgentId, availableModels: AVAILABLE_MODELS, }); break; case 'ping': sendResponse(msg.id, { pong: true }); break; default: sendError(msg.id, `Unbekannter Befehl: ${msg.command}`); } } // ============ Main ============ const rl = createInterface({ input: process.stdin }); rl.on('line', (line) => { if (!line.trim()) return; try { handleCommand(JSON.parse(line)); } catch (err) { process.stderr.write(`Ungültige Eingabe: ${err.message}\n`); } }); rl.on('close', () => { process.stderr.write('stdin geschlossen\n'); }); process.on('SIGTERM', () => { clearInterval(keepAlive); process.exit(0); }); process.on('SIGINT', () => { clearInterval(keepAlive); process.exit(0); }); // Bereit sendEvent('ready', { version: '1.1.0', pid: process.pid, model: currentModel, availableModels: AVAILABLE_MODELS });