Path: blob/main/src/vs/sessions/contrib/agentHost/browser/agentHostDiffs.ts
13401 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 { isDefined } from '../../../../base/common/types.js';6import { URI } from '../../../../base/common/uri.js';7import { SessionStatus as ProtocolSessionStatus } from '../../../../platform/agentHost/common/state/protocol/state.js';8import { ISessionFileDiff } from '../../../../platform/agentHost/common/state/sessionState.js';9import { IChatSessionFileChange2, isIChatSessionFileChange2 } from '../../../../workbench/contrib/chat/common/chatSessionsService.js';10import { ISessionFileChange, SessionStatus } from '../../../services/sessions/common/session.js';1112/**13* Maps the protocol-layer session status bitset to the UI-layer14* {@link SessionStatus} enum used by session adapters.15*/16export function mapProtocolStatus(protocol: ProtocolSessionStatus): SessionStatus {17if ((protocol & ProtocolSessionStatus.InputNeeded) === ProtocolSessionStatus.InputNeeded) {18return SessionStatus.NeedsInput;19}20if (protocol & ProtocolSessionStatus.InProgress) {21return SessionStatus.InProgress;22}23if (protocol & ProtocolSessionStatus.Error) {24return SessionStatus.Error;25}2627return SessionStatus.Completed;28}2930/**31* Converts agent host diffs to the chat session file change format.32*33* @param mapUri Optional URI mapper applied after parsing. The remote agent34* host provider uses this to rewrite `file:` URIs into agent-host URIs.35*/36export function diffsToChanges(diffs: readonly ISessionFileDiff[], mapUri?: (uri: URI) => URI): IChatSessionFileChange2[] {37return diffs.map(d => {38const rawUri = d.after?.uri ?? d.before?.uri;39if (!rawUri) {40return undefined;41}4243const uri = mapUri ? mapUri(URI.parse(rawUri)) : URI.parse(rawUri);4445// For deletions (no `after`), `modifiedUri` is `undefined` so the46// renderer treats the entry as a deletion and doesn't try to open the47// (now-missing) file as the "modified" side of the diff editor.48const modifiedUri = d.after49? (mapUri ? mapUri(URI.parse(d.after.uri)) : URI.parse(d.after.uri))50: undefined;5152// Use the before-content reference URI so the diff editor can53// fetch the snapshot of the file *before* the session's edits.54let originalUri: URI | undefined;55if (d.before?.content?.uri) {56const parsed = URI.parse(d.before.content.uri);57originalUri = mapUri ? mapUri(parsed) : parsed;58}5960return {61uri,62modifiedUri,63originalUri,64insertions: d.diff?.added ?? 0,65deletions: d.diff?.removed ?? 0,66} satisfies IChatSessionFileChange2;67}).filter(isDefined);68}6970/**71* Returns `true` when the current file changes already72* match the incoming diffs, avoiding unnecessary observable updates.73*/74export function diffsEqual(current: readonly ISessionFileChange[], diffs: readonly ISessionFileDiff[], mapUri?: (uri: URI) => URI): boolean {75if (current.length !== diffs.length) {76return false;77}78for (let i = 0; i < current.length; i++) {79const c = current[i];80const d = diffs[i];81const rawUri = d.after?.uri ?? d.before?.uri;82if (!rawUri) {83continue;84}85const parsed = URI.parse(rawUri);86const diffUri = mapUri ? mapUri(parsed) : parsed;87const cUri = isIChatSessionFileChange2(c) ? c.uri : c.modifiedUri;88if (cUri.toString() !== diffUri.toString() || c.insertions !== (d.diff?.added ?? 0) || c.deletions !== (d.diff?.removed ?? 0)) {89return false;90}9192const beforeContentUri = d.before?.content?.uri;93const currentOriginal = c.originalUri?.toString();94if (beforeContentUri) {95const parsedBefore = URI.parse(beforeContentUri);96const mappedBefore = mapUri ? mapUri(parsedBefore) : parsedBefore;97if (currentOriginal !== mappedBefore.toString()) {98return false;99}100} else if (currentOriginal) {101return false;102}103}104return true;105}106107108