Claude-Session-ID für SDK-Fortsetzung
- events.ts: Session-ID aus claude-result speichern via set_claude_session_id - claude.rs: load_claude_session_id() lädt ID der aktiven Session - claude.rs: send_to_bridge_full() mit resumeSessionId Parameter - claude-bridge.js: sendMessage() akzeptiert resumeSessionId - Bridge nutzt sessionId in query() Optionen für SDK-Fortsetzung Ermöglicht nahtlose Konversations-Fortsetzung auf SDK-Ebene. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3f600b828e
commit
be65dee04a
3 changed files with 78 additions and 14 deletions
|
|
@ -119,7 +119,7 @@ function summarizeToolInput(tool, input) {
|
|||
|
||||
// ============ Claude Agent SDK ============
|
||||
|
||||
async function sendMessage(message, requestId, model = null, contextOverride = null) {
|
||||
async function sendMessage(message, requestId, model = null, contextOverride = null, resumeSessionId = null) {
|
||||
// Modell für diese Anfrage (Parameter > State > Default)
|
||||
const useModel = model || currentModel;
|
||||
|
||||
|
|
@ -129,31 +129,37 @@ async function sendMessage(message, requestId, model = null, contextOverride = n
|
|||
currentAgentId = randomUUID();
|
||||
activeAbort = new AbortController();
|
||||
|
||||
const isResuming = !!resumeSessionId;
|
||||
|
||||
sendEvent('agent-started', {
|
||||
id: currentAgentId,
|
||||
type: 'Main',
|
||||
task: message.substring(0, 100),
|
||||
model: useModel,
|
||||
resuming: isResuming,
|
||||
});
|
||||
|
||||
// Monitor: Agent gestartet
|
||||
sendMonitorEvent('agent', `Main Agent gestartet (${useModel})`, {
|
||||
const resumeInfo = isResuming ? ' (Fortsetzung)' : '';
|
||||
sendMonitorEvent('agent', `Main Agent gestartet (${useModel})${resumeInfo}`, {
|
||||
agentId: currentAgentId,
|
||||
model: useModel,
|
||||
task: message.substring(0, 100),
|
||||
contextTokens: useContext ? Math.ceil(useContext.length / 4) : 0,
|
||||
resumeSessionId: resumeSessionId || null,
|
||||
});
|
||||
|
||||
// Monitor: API-Request
|
||||
const contextInfo = useContext ? ` +${Math.ceil(useContext.length / 4)} ctx` : '';
|
||||
sendMonitorEvent('api', `→ ${useModel}${contextInfo}`, {
|
||||
sendMonitorEvent('api', `→ ${useModel}${contextInfo}${resumeInfo}`, {
|
||||
model: useModel,
|
||||
promptLength: message.length,
|
||||
contextLength: useContext?.length || 0,
|
||||
maxTurns: 25,
|
||||
resumeSessionId: resumeSessionId || null,
|
||||
});
|
||||
|
||||
sendResponse(requestId, { agentId: currentAgentId, status: 'gestartet', model: useModel });
|
||||
sendResponse(requestId, { agentId: currentAgentId, status: 'gestartet', model: useModel, resuming: isResuming });
|
||||
|
||||
// Nachricht mit Context kombinieren
|
||||
const fullPrompt = useContext
|
||||
|
|
@ -165,13 +171,21 @@ async function sendMessage(message, requestId, model = null, contextOverride = n
|
|||
let usedModel = useModel;
|
||||
|
||||
try {
|
||||
const conversation = query({
|
||||
prompt: fullPrompt,
|
||||
options: {
|
||||
// Query-Optionen zusammenstellen
|
||||
const queryOptions = {
|
||||
model: useModel,
|
||||
maxTurns: 25,
|
||||
abortController: activeAbort,
|
||||
},
|
||||
};
|
||||
|
||||
// Session-ID für Fortsetzung hinzufügen wenn vorhanden
|
||||
if (resumeSessionId) {
|
||||
queryOptions.sessionId = resumeSessionId;
|
||||
}
|
||||
|
||||
const conversation = query({
|
||||
prompt: fullPrompt,
|
||||
options: queryOptions,
|
||||
});
|
||||
|
||||
for await (const event of conversation) {
|
||||
|
|
@ -342,8 +356,8 @@ function handleCommand(msg) {
|
|||
sendError(msg.id, 'Keine Nachricht angegeben');
|
||||
return;
|
||||
}
|
||||
// Modell und Context können pro Anfrage überschrieben werden
|
||||
sendMessage(msg.message, msg.id, msg.model, msg.context);
|
||||
// Modell, Context und Resume-Session-ID können pro Anfrage überschrieben werden
|
||||
sendMessage(msg.message, msg.id, msg.model, msg.context, msg.resumeSessionId);
|
||||
break;
|
||||
|
||||
case 'set-context':
|
||||
|
|
|
|||
|
|
@ -259,11 +259,22 @@ fn handle_bridge_message(app: &AppHandle, msg: BridgeMessage) {
|
|||
|
||||
/// Befehl an Bridge senden
|
||||
fn send_to_bridge(app: &AppHandle, command: &str, message: &str) -> Result<String, String> {
|
||||
send_to_bridge_with_context(app, command, message, None)
|
||||
send_to_bridge_full(app, command, message, None, None)
|
||||
}
|
||||
|
||||
/// Befehl an Bridge senden mit optionalem Context
|
||||
fn send_to_bridge_with_context(app: &AppHandle, command: &str, message: &str, context: Option<String>) -> Result<String, String> {
|
||||
send_to_bridge_full(app, command, message, context, None)
|
||||
}
|
||||
|
||||
/// Befehl an Bridge senden mit Context und Resume-Session-ID
|
||||
fn send_to_bridge_full(
|
||||
app: &AppHandle,
|
||||
command: &str,
|
||||
message: &str,
|
||||
context: Option<String>,
|
||||
resume_session_id: Option<String>,
|
||||
) -> Result<String, String> {
|
||||
let state = app.state::<Arc<Mutex<ClaudeState>>>();
|
||||
let mut state = state.lock().unwrap();
|
||||
|
||||
|
|
@ -289,6 +300,12 @@ fn send_to_bridge_with_context(app: &AppHandle, command: &str, message: &str, co
|
|||
payload["context"] = serde_json::Value::String(ctx);
|
||||
}
|
||||
}
|
||||
// Resume-Session-ID hinzufügen wenn vorhanden
|
||||
if let Some(sid) = resume_session_id {
|
||||
if !sid.is_empty() {
|
||||
payload["resumeSessionId"] = serde_json::Value::String(sid);
|
||||
}
|
||||
}
|
||||
payload
|
||||
},
|
||||
"set-context" | "clear-context" => serde_json::json!({
|
||||
|
|
@ -339,12 +356,29 @@ pub async fn send_message(app: AppHandle, message: String) -> Result<String, Str
|
|||
println!("📌 Sticky Context geladen (~{} Token)", context.as_ref().map(|c| c.len() / 4).unwrap_or(0));
|
||||
}
|
||||
|
||||
send_to_bridge_with_context(&app, "message", &message, context)?;
|
||||
// Claude-Session-ID für Fortsetzung laden
|
||||
let resume_session_id = load_claude_session_id(&app);
|
||||
if resume_session_id.is_some() {
|
||||
println!("🔗 Session fortsetzen mit Claude-ID: {:?}", resume_session_id);
|
||||
}
|
||||
|
||||
send_to_bridge_full(&app, "message", &message, context, resume_session_id)?;
|
||||
|
||||
// Hinweis: Die eigentliche Antwort kommt über Events
|
||||
Ok("Nachricht gesendet. Antwort folgt über Events.".to_string())
|
||||
}
|
||||
|
||||
/// Claude-Session-ID der aktiven Session laden
|
||||
fn load_claude_session_id(app: &AppHandle) -> Option<String> {
|
||||
if let Some(db_state) = app.try_state::<Arc<Mutex<db::Database>>>() {
|
||||
let db = db_state.lock().ok()?;
|
||||
if let Ok(Some(session)) = db.get_active_session() {
|
||||
return session.claude_session_id;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Sticky Context aus DB laden und als Prompt-Text rendern
|
||||
fn load_sticky_context_for_prompt(app: &AppHandle) -> Option<String> {
|
||||
use crate::context;
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ export async function initEventListeners(): Promise<void> {
|
|||
listeners.push(
|
||||
await listen<ResultEvent>('claude-result', async (event) => {
|
||||
const { cost, tokens, session_id, model } = event.payload;
|
||||
console.log('📊 Ergebnis:', { cost: cost ? `$${cost.toFixed(4)}` : '-', tokens, model });
|
||||
console.log('📊 Ergebnis:', { cost: cost ? `$${cost.toFixed(4)}` : '-', tokens, model, session_id });
|
||||
|
||||
// Modell an die Streaming-Nachricht anhängen und speichern
|
||||
if (streamingMessageId) {
|
||||
|
|
@ -261,6 +261,22 @@ export async function initEventListeners(): Promise<void> {
|
|||
currentModel.set(model);
|
||||
}
|
||||
|
||||
// Claude Session-ID speichern für Fortsetzung
|
||||
if (session_id) {
|
||||
const appSessionId = get(currentSessionId);
|
||||
if (appSessionId) {
|
||||
try {
|
||||
await invoke('set_claude_session_id', {
|
||||
sessionId: appSessionId,
|
||||
claudeSessionId: session_id,
|
||||
});
|
||||
console.log('🔗 Claude Session-ID gespeichert:', session_id);
|
||||
} catch (err) {
|
||||
console.warn('Claude Session-ID konnte nicht gespeichert werden:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Session-Statistiken aktualisieren
|
||||
if (tokens || cost) {
|
||||
sessionStats.update((s) => ({
|
||||
|
|
|
|||
Loading…
Reference in a new issue