feat: implement SDK session ID handling for conversation continuity

- Added support for resuming conversations using the Claude SDK session ID.
- Updated the ClaudeProvider to conditionally resume sessions based on the presence of a session ID and conversation history.
- Enhanced the AgentService to capture and store the SDK session ID from incoming messages, ensuring continuity in conversations.
This commit is contained in:
SuperComboGamer
2025-12-14 11:02:42 -05:00
parent ebc4f1422a
commit 5a1fe23ddb
3 changed files with 21 additions and 65 deletions

View File

@@ -7,10 +7,6 @@
import { query, type Options } from "@anthropic-ai/claude-agent-sdk";
import { BaseProvider } from "./base-provider.js";
import {
convertHistoryToMessages,
normalizeContentBlocks,
} from "../lib/conversation-utils.js";
import type {
ExecuteOptions,
ProviderMessage,
@@ -38,6 +34,7 @@ export class ClaudeProvider extends BaseProvider {
allowedTools,
abortController,
conversationHistory,
sdkSessionId,
} = options;
// Build Claude SDK options
@@ -65,69 +62,17 @@ export class ClaudeProvider extends BaseProvider {
autoAllowBashIfSandboxed: true,
},
abortController,
// Resume existing SDK session if we have a session ID
...(sdkSessionId && conversationHistory && conversationHistory.length > 0
? { resume: sdkSessionId }
: {}),
};
// Build prompt payload with conversation history
let promptPayload: string | AsyncGenerator<any, void, unknown> | Array<any>;
// Build prompt payload
let promptPayload: string | AsyncIterable<any>;
if (conversationHistory && conversationHistory.length > 0) {
// Multi-turn conversation with history
// Convert history to SDK message format
// Note: When using async generator, SDK only accepts SDKUserMessage (type: 'user')
// So we filter to only include user messages to avoid SDK errors
const historyMessages = convertHistoryToMessages(conversationHistory);
const hasAssistantMessages = historyMessages.some(
(msg) => msg.type === "assistant"
);
if (hasAssistantMessages) {
// If we have assistant messages, use async generator but filter to only user messages
// This maintains conversation flow while respecting SDK type constraints
promptPayload = (async function* () {
// Filter to only user messages - SDK async generator only accepts SDKUserMessage
const userHistoryMessages = historyMessages.filter(
(msg) => msg.type === "user"
);
for (const msg of userHistoryMessages) {
yield msg;
}
// Yield current prompt
const normalizedPrompt = normalizeContentBlocks(prompt);
const currentPrompt = {
type: "user" as const,
session_id: "",
message: {
role: "user" as const,
content: normalizedPrompt,
},
parent_tool_use_id: null,
};
yield currentPrompt;
})();
} else {
// Only user messages in history - can use async generator normally
promptPayload = (async function* () {
for (const msg of historyMessages) {
yield msg;
}
// Yield current prompt
const normalizedPrompt = normalizeContentBlocks(prompt);
const currentPrompt = {
type: "user" as const,
session_id: "",
message: {
role: "user" as const,
content: normalizedPrompt,
},
parent_tool_use_id: null,
};
yield currentPrompt;
})();
}
} else if (Array.isArray(prompt)) {
// Multi-part prompt (with images) - no history
if (Array.isArray(prompt)) {
// Multi-part prompt (with images)
promptPayload = (async function* () {
const multiPartPrompt = {
type: "user" as const,
@@ -141,7 +86,7 @@ export class ClaudeProvider extends BaseProvider {
yield multiPartPrompt;
})();
} else {
// Simple text prompt - no history
// Simple text prompt
promptPayload = prompt;
}

View File

@@ -32,6 +32,7 @@ export interface ExecuteOptions {
mcpServers?: Record<string, unknown>;
abortController?: AbortController;
conversationHistory?: ConversationMessage[]; // Previous messages for context
sdkSessionId?: string; // Claude SDK session ID for resuming conversations
}
/**

View File

@@ -33,6 +33,7 @@ interface Session {
abortController: AbortController | null;
workingDirectory: string;
model?: string;
sdkSessionId?: string; // Claude SDK session ID for conversation continuity
}
interface SessionMetadata {
@@ -200,6 +201,7 @@ export class AgentService {
abortController: session.abortController!,
conversationHistory:
conversationHistory.length > 0 ? conversationHistory : undefined,
sdkSessionId: session.sdkSessionId, // Pass SDK session ID for resuming
};
// Build prompt content with images
@@ -221,6 +223,14 @@ export class AgentService {
const toolUses: Array<{ name: string; input: unknown }> = [];
for await (const msg of stream) {
// Capture SDK session ID from any message
if (msg.session_id && !session.sdkSessionId) {
session.sdkSessionId = msg.session_id;
console.log(
`[AgentService] Captured SDK session ID: ${msg.session_id}`
);
}
if (msg.type === "assistant") {
if (msg.message?.content) {
for (const block of msg.message.content) {