mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user