Path: blob/main/extensions/copilot/src/extension/chatSessions/claude/node/claudeSessionStateService.ts
13405 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*--------------------------------------------------------------------------------------------*/45import { EffortLevel, PermissionMode } from '@anthropic-ai/claude-agent-sdk';6import { CapturingToken } from '../../../../platform/requestLogger/common/capturingToken';7import type { TraceContext } from '../../../../platform/otel/common/otelService';8import { arrayEquals } from '../../../../util/vs/base/common/equals';9import { Emitter } from '../../../../util/vs/base/common/event';10import { Disposable } from '../../../../util/vs/base/common/lifecycle';11import type { ClaudeFolderInfo } from '../common/claudeFolderInfo';12import type { ParsedClaudeModelId } from '../common/claudeModelId';13import type { IClaudeSessionStateService, SessionState, SessionStateChangeEvent, UsageHandler } from '../common/claudeSessionStateService';1415export class ClaudeSessionStateService extends Disposable implements IClaudeSessionStateService {16declare _serviceBrand: undefined;1718private readonly _onDidChangeSessionState = this._register(new Emitter<SessionStateChangeEvent>());19readonly onDidChangeSessionState = this._onDidChangeSessionState.event;2021// State for sessions (model and permission mode selections)22// TODO: What about expiration of state for old sessions?23// TODO: Refactor setters to use a single `updateSession(id, patch)` method or spread24// pattern (`{ ...existing, field: value }`) so that adding a new field to SessionState25// doesn't require touching every existing setter.26private readonly _sessionState = new Map<string, SessionState>();2728constructor() {29super();30}3132getModelIdForSession(sessionId: string): ParsedClaudeModelId | undefined {33const state = this._sessionState.get(sessionId);34return state?.modelId;35}3637setModelIdForSession(sessionId: string, modelId: ParsedClaudeModelId | undefined): void {38const existing = this._sessionState.get(sessionId);39if (existing?.modelId === modelId) {40return;41}42this._sessionState.set(sessionId, {43modelId,44permissionMode: existing?.permissionMode ?? 'acceptEdits',45capturingToken: existing?.capturingToken,46folderInfo: existing?.folderInfo,47usageHandler: existing?.usageHandler,48reasoningEffort: existing?.reasoningEffort,49traceContext: existing?.traceContext,50});51this._onDidChangeSessionState.fire({ sessionId, modelId });52}5354getPermissionModeForSession(sessionId: string): PermissionMode {55return this._sessionState.get(sessionId)?.permissionMode ?? 'acceptEdits';56}5758setPermissionModeForSession(sessionId: string, mode: PermissionMode): void {59const existing = this._sessionState.get(sessionId);60if (existing?.permissionMode === mode) {61return;62}63this._sessionState.set(sessionId, {64modelId: existing?.modelId,65permissionMode: mode,66capturingToken: existing?.capturingToken,67folderInfo: existing?.folderInfo,68usageHandler: existing?.usageHandler,69reasoningEffort: existing?.reasoningEffort,70traceContext: existing?.traceContext,71});72this._onDidChangeSessionState.fire({ sessionId, permissionMode: mode });73}7475getCapturingTokenForSession(sessionId: string): CapturingToken | undefined {76return this._sessionState.get(sessionId)?.capturingToken;77}7879setCapturingTokenForSession(sessionId: string, token: CapturingToken | undefined): void {80const existing = this._sessionState.get(sessionId);81this._sessionState.set(sessionId, {82modelId: existing?.modelId,83permissionMode: existing?.permissionMode ?? 'acceptEdits',84capturingToken: token,85folderInfo: existing?.folderInfo,86usageHandler: existing?.usageHandler,87reasoningEffort: existing?.reasoningEffort,88traceContext: existing?.traceContext,89});90}9192getFolderInfoForSession(sessionId: string): ClaudeFolderInfo | undefined {93return this._sessionState.get(sessionId)?.folderInfo;94}9596setFolderInfoForSession(sessionId: string, folderInfo: ClaudeFolderInfo): void {97const existing = this._sessionState.get(sessionId);98if (existing?.folderInfo?.cwd === folderInfo.cwd && arrayEquals(existing?.folderInfo?.additionalDirectories ?? [], folderInfo.additionalDirectories)) {99return;100}101this._sessionState.set(sessionId, {102modelId: existing?.modelId,103permissionMode: existing?.permissionMode ?? 'acceptEdits',104capturingToken: existing?.capturingToken,105folderInfo,106usageHandler: existing?.usageHandler,107reasoningEffort: existing?.reasoningEffort,108traceContext: existing?.traceContext,109});110this._onDidChangeSessionState.fire({ sessionId, folderInfo });111}112113getUsageHandlerForSession(sessionId: string): UsageHandler | undefined {114return this._sessionState.get(sessionId)?.usageHandler;115}116117setUsageHandlerForSession(sessionId: string, handler: UsageHandler | undefined): void {118const existing = this._sessionState.get(sessionId);119this._sessionState.set(sessionId, {120modelId: existing?.modelId,121permissionMode: existing?.permissionMode ?? 'acceptEdits',122capturingToken: existing?.capturingToken,123folderInfo: existing?.folderInfo,124usageHandler: handler,125reasoningEffort: existing?.reasoningEffort,126traceContext: existing?.traceContext,127});128}129130getReasoningEffortForSession(sessionId: string): EffortLevel | undefined {131return this._sessionState.get(sessionId)?.reasoningEffort;132}133134setReasoningEffortForSession(sessionId: string, effort: EffortLevel | undefined): void {135const existing = this._sessionState.get(sessionId);136if (existing?.reasoningEffort === effort) {137return;138}139this._sessionState.set(sessionId, {140modelId: existing?.modelId,141permissionMode: existing?.permissionMode ?? 'acceptEdits',142capturingToken: existing?.capturingToken,143folderInfo: existing?.folderInfo,144usageHandler: existing?.usageHandler,145reasoningEffort: effort,146traceContext: existing?.traceContext,147});148}149150getTraceContextForSession(sessionId: string): TraceContext | undefined {151return this._sessionState.get(sessionId)?.traceContext;152}153154setTraceContextForSession(sessionId: string, traceContext: TraceContext | undefined): void {155const existing = this._sessionState.get(sessionId);156this._sessionState.set(sessionId, {157modelId: existing?.modelId,158permissionMode: existing?.permissionMode ?? 'acceptEdits',159capturingToken: existing?.capturingToken,160folderInfo: existing?.folderInfo,161usageHandler: existing?.usageHandler,162reasoningEffort: existing?.reasoningEffort,163traceContext,164});165}166167override dispose(): void {168this._sessionState.clear();169super.dispose();170}171}172173174