Path: blob/main/extensions/copilot/src/extension/chatSessions/claude/node/sessionParser/sdkSessionAdapter.ts
13406 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45/**6* SDK Session Adapter7*8* Thin conversion layer between the `@anthropic-ai/claude-agent-sdk` session APIs9* and our internal types (`IClaudeCodeSessionInfo`, `IClaudeCodeSession`, `StoredMessage`).10*11* The SDK provides:12* - `listSessions()` / `getSessionInfo()` → `SDKSessionInfo`13* - `getSessionMessages()` → `SessionMessage[]`14*15* This adapter converts those into the shapes consumed by `chatHistoryBuilder.ts`16* and `claudeChatSessionContentProvider.ts` without any JSONL parsing.17*/1819import type { SDKSessionInfo, SessionMessage } from '@anthropic-ai/claude-agent-sdk';20import {21AssistantMessageContent,22IClaudeCodeSession,23IClaudeCodeSessionInfo,24ISubagentSession,25StoredMessage,26UserMessageContent,27vAssistantMessageContent,28vUserMessageContent,29} from './claudeSessionSchema';3031// #region Label Helpers3233/**34* Strips `<system-reminder>` tags and their content from a string.35* The SDK includes raw system-reminder blocks in `summary` and `firstPrompt` fields.36*/37function stripSystemReminders(text: string): string {38return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>\s*/g, '').trim();39}4041/**42* Computes a clean display label from SDK session info.43* Priority: customTitle > stripped summary > stripped firstPrompt > fallback.44*/45function computeSessionLabel(info: SDKSessionInfo): string {46if (info.customTitle) {47return info.customTitle;48}4950const summary = info.summary ? stripSystemReminders(info.summary) : '';51if (summary) {52return truncateLabel(summary);53}5455const firstPrompt = info.firstPrompt ? stripSystemReminders(info.firstPrompt) : '';56if (firstPrompt) {57return truncateLabel(firstPrompt);58}5960return 'Claude Session';61}6263const MAX_LABEL_LENGTH = 50;6465function truncateLabel(text: string): string {66const singleLine = text.replace(/\s+/g, ' ');67if (singleLine.length <= MAX_LABEL_LENGTH) {68return singleLine;69}70return singleLine.slice(0, MAX_LABEL_LENGTH) + '…';71}7273// #endregion7475// #region SDKSessionInfo → IClaudeCodeSessionInfo7677/**78* Converts an `SDKSessionInfo` (from `listSessions` / `getSessionInfo`) into79* the lightweight `IClaudeCodeSessionInfo` used by the session list UI.80*/81export function sdkSessionInfoToSessionInfo(82info: SDKSessionInfo,83folderName?: string,84): IClaudeCodeSessionInfo {85return {86id: info.sessionId,87label: computeSessionLabel(info),88created: info.createdAt ?? info.lastModified,89lastRequestEnded: info.lastModified,90folderName,91cwd: info.cwd,92gitBranch: info.gitBranch,93};94}9596// #endregion9798// #region SessionMessage → StoredMessage99100/**101* Converts an array of `SessionMessage` (from `getSessionMessages`) into102* `StoredMessage[]` compatible with `chatHistoryBuilder.ts`.103*104* Uses existing validators (`vUserMessageContent`, `vAssistantMessageContent`)105* to narrow the `message: unknown` field into typed content.106*107* Messages that fail validation are silently skipped — this matches the parser108* behavior of ignoring malformed JSONL entries.109*/110export function sdkSessionMessagesToStoredMessages(111messages: readonly SessionMessage[],112): StoredMessage[] {113const result: StoredMessage[] = [];114115for (const msg of messages) {116const stored = sdkSessionMessageToStoredMessage(msg);117if (stored) {118result.push(stored);119}120}121122return result;123}124125function sdkSessionMessageToStoredMessage(126msg: SessionMessage,127): StoredMessage | undefined {128if (msg.type === 'user') {129const validated = vUserMessageContent.validate(msg.message);130if (validated.error) {131return undefined;132}133return {134uuid: msg.uuid,135sessionId: msg.session_id,136timestamp: new Date(0),137parentUuid: null,138type: 'user',139message: validated.content as UserMessageContent,140};141}142143if (msg.type === 'assistant') {144const validated = vAssistantMessageContent.validate(msg.message);145if (validated.error) {146return undefined;147}148return {149uuid: msg.uuid,150sessionId: msg.session_id,151timestamp: new Date(0),152parentUuid: null,153type: 'assistant',154message: validated.content as AssistantMessageContent,155};156}157158return undefined;159}160161// #endregion162163// #region Subagent Session Building164165function extractParentToolUseId(messages: readonly SessionMessage[]): string | undefined {166for (const msg of messages) {167if (msg.type !== 'assistant' || msg.message === null || typeof msg.message !== 'object') {168continue;169}170if ('parent_tool_use_id' in msg.message) {171const id = msg.message.parent_tool_use_id;172if (typeof id === 'string') {173return id;174}175}176}177return undefined;178}179180/**181* Converts SDK `SessionMessage[]` (from `getSubagentMessages`) into an182* `ISubagentSession` for display in the chat history.183*184* Extracts `parent_tool_use_id` from the first assistant message that185* contains one, to link the subagent back to its spawning Agent tool_use186* in the parent session.187*/188export function sdkSubagentMessagesToSubagentSession(189agentId: string,190messages: readonly SessionMessage[],191): ISubagentSession | null {192const storedMessages = sdkSessionMessagesToStoredMessages(messages);193if (storedMessages.length === 0) {194return null;195}196197return {198agentId,199parentToolUseId: extractParentToolUseId(messages),200messages: storedMessages,201timestamp: storedMessages[storedMessages.length - 1].timestamp,202};203}204205// #endregion206207// #region Full Session Assembly208209/**210* Assembles a full `IClaudeCodeSession` from SDK data and separately-loaded subagents.211*/212export function buildClaudeCodeSession(213info: SDKSessionInfo,214messages: readonly SessionMessage[],215subagents: readonly ISubagentSession[],216folderName?: string,217): IClaudeCodeSession {218const sessionInfo = sdkSessionInfoToSessionInfo(info, folderName);219const storedMessages = sdkSessionMessagesToStoredMessages(messages);220221return {222...sessionInfo,223messages: storedMessages,224subagents,225};226}227228// #endregion229230231