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 ============
|
// ============ 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)
|
// Modell für diese Anfrage (Parameter > State > Default)
|
||||||
const useModel = model || currentModel;
|
const useModel = model || currentModel;
|
||||||
|
|
||||||
|
|
@ -129,31 +129,37 @@ async function sendMessage(message, requestId, model = null, contextOverride = n
|
||||||
currentAgentId = randomUUID();
|
currentAgentId = randomUUID();
|
||||||
activeAbort = new AbortController();
|
activeAbort = new AbortController();
|
||||||
|
|
||||||
|
const isResuming = !!resumeSessionId;
|
||||||
|
|
||||||
sendEvent('agent-started', {
|
sendEvent('agent-started', {
|
||||||
id: currentAgentId,
|
id: currentAgentId,
|
||||||
type: 'Main',
|
type: 'Main',
|
||||||
task: message.substring(0, 100),
|
task: message.substring(0, 100),
|
||||||
model: useModel,
|
model: useModel,
|
||||||
|
resuming: isResuming,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Monitor: Agent gestartet
|
// Monitor: Agent gestartet
|
||||||
sendMonitorEvent('agent', `Main Agent gestartet (${useModel})`, {
|
const resumeInfo = isResuming ? ' (Fortsetzung)' : '';
|
||||||
|
sendMonitorEvent('agent', `Main Agent gestartet (${useModel})${resumeInfo}`, {
|
||||||
agentId: currentAgentId,
|
agentId: currentAgentId,
|
||||||
model: useModel,
|
model: useModel,
|
||||||
task: message.substring(0, 100),
|
task: message.substring(0, 100),
|
||||||
contextTokens: useContext ? Math.ceil(useContext.length / 4) : 0,
|
contextTokens: useContext ? Math.ceil(useContext.length / 4) : 0,
|
||||||
|
resumeSessionId: resumeSessionId || null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Monitor: API-Request
|
// Monitor: API-Request
|
||||||
const contextInfo = useContext ? ` +${Math.ceil(useContext.length / 4)} ctx` : '';
|
const contextInfo = useContext ? ` +${Math.ceil(useContext.length / 4)} ctx` : '';
|
||||||
sendMonitorEvent('api', `→ ${useModel}${contextInfo}`, {
|
sendMonitorEvent('api', `→ ${useModel}${contextInfo}${resumeInfo}`, {
|
||||||
model: useModel,
|
model: useModel,
|
||||||
promptLength: message.length,
|
promptLength: message.length,
|
||||||
contextLength: useContext?.length || 0,
|
contextLength: useContext?.length || 0,
|
||||||
maxTurns: 25,
|
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
|
// Nachricht mit Context kombinieren
|
||||||
const fullPrompt = useContext
|
const fullPrompt = useContext
|
||||||
|
|
@ -165,13 +171,21 @@ async function sendMessage(message, requestId, model = null, contextOverride = n
|
||||||
let usedModel = useModel;
|
let usedModel = useModel;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const conversation = query({
|
// Query-Optionen zusammenstellen
|
||||||
prompt: fullPrompt,
|
const queryOptions = {
|
||||||
options: {
|
|
||||||
model: useModel,
|
model: useModel,
|
||||||
maxTurns: 25,
|
maxTurns: 25,
|
||||||
abortController: activeAbort,
|
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) {
|
for await (const event of conversation) {
|
||||||
|
|
@ -342,8 +356,8 @@ function handleCommand(msg) {
|
||||||
sendError(msg.id, 'Keine Nachricht angegeben');
|
sendError(msg.id, 'Keine Nachricht angegeben');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Modell und Context können pro Anfrage überschrieben werden
|
// Modell, Context und Resume-Session-ID können pro Anfrage überschrieben werden
|
||||||
sendMessage(msg.message, msg.id, msg.model, msg.context);
|
sendMessage(msg.message, msg.id, msg.model, msg.context, msg.resumeSessionId);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'set-context':
|
case 'set-context':
|
||||||
|
|
|
||||||
|
|
@ -259,11 +259,22 @@ fn handle_bridge_message(app: &AppHandle, msg: BridgeMessage) {
|
||||||
|
|
||||||
/// Befehl an Bridge senden
|
/// Befehl an Bridge senden
|
||||||
fn send_to_bridge(app: &AppHandle, command: &str, message: &str) -> Result<String, String> {
|
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
|
/// 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> {
|
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 state = app.state::<Arc<Mutex<ClaudeState>>>();
|
||||||
let mut state = state.lock().unwrap();
|
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);
|
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
|
payload
|
||||||
},
|
},
|
||||||
"set-context" | "clear-context" => serde_json::json!({
|
"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));
|
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
|
// Hinweis: Die eigentliche Antwort kommt über Events
|
||||||
Ok("Nachricht gesendet. Antwort folgt über Events.".to_string())
|
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
|
/// Sticky Context aus DB laden und als Prompt-Text rendern
|
||||||
fn load_sticky_context_for_prompt(app: &AppHandle) -> Option<String> {
|
fn load_sticky_context_for_prompt(app: &AppHandle) -> Option<String> {
|
||||||
use crate::context;
|
use crate::context;
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,7 @@ export async function initEventListeners(): Promise<void> {
|
||||||
listeners.push(
|
listeners.push(
|
||||||
await listen<ResultEvent>('claude-result', async (event) => {
|
await listen<ResultEvent>('claude-result', async (event) => {
|
||||||
const { cost, tokens, session_id, model } = event.payload;
|
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
|
// Modell an die Streaming-Nachricht anhängen und speichern
|
||||||
if (streamingMessageId) {
|
if (streamingMessageId) {
|
||||||
|
|
@ -261,6 +261,22 @@ export async function initEventListeners(): Promise<void> {
|
||||||
currentModel.set(model);
|
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
|
// Session-Statistiken aktualisieren
|
||||||
if (tokens || cost) {
|
if (tokens || cost) {
|
||||||
sessionStats.update((s) => ({
|
sessionStats.update((s) => ({
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue