mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +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 { query, type Options } from "@anthropic-ai/claude-agent-sdk";
|
||||||
import { BaseProvider } from "./base-provider.js";
|
import { BaseProvider } from "./base-provider.js";
|
||||||
import {
|
|
||||||
convertHistoryToMessages,
|
|
||||||
normalizeContentBlocks,
|
|
||||||
} from "../lib/conversation-utils.js";
|
|
||||||
import type {
|
import type {
|
||||||
ExecuteOptions,
|
ExecuteOptions,
|
||||||
ProviderMessage,
|
ProviderMessage,
|
||||||
@@ -38,6 +34,7 @@ export class ClaudeProvider extends BaseProvider {
|
|||||||
allowedTools,
|
allowedTools,
|
||||||
abortController,
|
abortController,
|
||||||
conversationHistory,
|
conversationHistory,
|
||||||
|
sdkSessionId,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
// Build Claude SDK options
|
// Build Claude SDK options
|
||||||
@@ -65,69 +62,17 @@ export class ClaudeProvider extends BaseProvider {
|
|||||||
autoAllowBashIfSandboxed: true,
|
autoAllowBashIfSandboxed: true,
|
||||||
},
|
},
|
||||||
abortController,
|
abortController,
|
||||||
|
// Resume existing SDK session if we have a session ID
|
||||||
|
...(sdkSessionId && conversationHistory && conversationHistory.length > 0
|
||||||
|
? { resume: sdkSessionId }
|
||||||
|
: {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build prompt payload with conversation history
|
// Build prompt payload
|
||||||
let promptPayload: string | AsyncGenerator<any, void, unknown> | Array<any>;
|
let promptPayload: string | AsyncIterable<any>;
|
||||||
|
|
||||||
if (conversationHistory && conversationHistory.length > 0) {
|
if (Array.isArray(prompt)) {
|
||||||
// Multi-turn conversation with history
|
// Multi-part prompt (with images)
|
||||||
// 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
|
|
||||||
promptPayload = (async function* () {
|
promptPayload = (async function* () {
|
||||||
const multiPartPrompt = {
|
const multiPartPrompt = {
|
||||||
type: "user" as const,
|
type: "user" as const,
|
||||||
@@ -141,7 +86,7 @@ export class ClaudeProvider extends BaseProvider {
|
|||||||
yield multiPartPrompt;
|
yield multiPartPrompt;
|
||||||
})();
|
})();
|
||||||
} else {
|
} else {
|
||||||
// Simple text prompt - no history
|
// Simple text prompt
|
||||||
promptPayload = prompt;
|
promptPayload = prompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export interface ExecuteOptions {
|
|||||||
mcpServers?: Record<string, unknown>;
|
mcpServers?: Record<string, unknown>;
|
||||||
abortController?: AbortController;
|
abortController?: AbortController;
|
||||||
conversationHistory?: ConversationMessage[]; // Previous messages for context
|
conversationHistory?: ConversationMessage[]; // Previous messages for context
|
||||||
|
sdkSessionId?: string; // Claude SDK session ID for resuming conversations
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ interface Session {
|
|||||||
abortController: AbortController | null;
|
abortController: AbortController | null;
|
||||||
workingDirectory: string;
|
workingDirectory: string;
|
||||||
model?: string;
|
model?: string;
|
||||||
|
sdkSessionId?: string; // Claude SDK session ID for conversation continuity
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SessionMetadata {
|
interface SessionMetadata {
|
||||||
@@ -200,6 +201,7 @@ export class AgentService {
|
|||||||
abortController: session.abortController!,
|
abortController: session.abortController!,
|
||||||
conversationHistory:
|
conversationHistory:
|
||||||
conversationHistory.length > 0 ? conversationHistory : undefined,
|
conversationHistory.length > 0 ? conversationHistory : undefined,
|
||||||
|
sdkSessionId: session.sdkSessionId, // Pass SDK session ID for resuming
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build prompt content with images
|
// Build prompt content with images
|
||||||
@@ -221,6 +223,14 @@ export class AgentService {
|
|||||||
const toolUses: Array<{ name: string; input: unknown }> = [];
|
const toolUses: Array<{ name: string; input: unknown }> = [];
|
||||||
|
|
||||||
for await (const msg of stream) {
|
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.type === "assistant") {
|
||||||
if (msg.message?.content) {
|
if (msg.message?.content) {
|
||||||
for (const block of msg.message.content) {
|
for (const block of msg.message.content) {
|
||||||
|
|||||||
Reference in New Issue
Block a user