mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-21 23:59:34 +01:00
ca80313cee
* Initial plan * Add trajectory format types and core implementation Co-authored-by: zhichli <57812115+zhichli@users.noreply.github.com> * Add unit tests for trajectory logger and fix implementation Co-authored-by: zhichli <57812115+zhichli@users.noreply.github.com> * Add comprehensive documentation for trajectory format Co-authored-by: zhichli <57812115+zhichli@users.noreply.github.com> * Add implementation status and next steps documentation Co-authored-by: zhichli <57812115+zhichli@users.noreply.github.com> * Add trajectory implementation quick reference summary * Add trajectory export commands and enhance trajectory logging functionality * Refactor trajectory metrics calculation and update schema version to ATIF v1.5 * trajectory scaffolding * Enhance trajectory tracking by adding subAgentName and agentName to tool metadata in SearchSubagentTool and TrajectoryLoggerAdapter * add export trajectory cmd for tree nodes * use sessionId for main trajectory * Update command categories from 'Copilot' to 'Chat' in package.json * Update trajectory schema version to ATIF-v1.5 and enhance error handling in export commands * Remove obsolete trajectory documentation and integration files * Refactor trajectory export commands and update README for clarity * Update src/platform/trajectory/common/trajectoryLogger.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/platform/trajectory/node/trajectoryLogger.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/platform/trajectory/node/trajectoryLoggerAdapter.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/extension/trajectory/vscode-node/trajectoryExportCommands.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Refactor adapter to avoid double trajectory start and add comprehensive tests - Remove redundant second startTrajectory call in adapter sync loop - Add 12 comprehensive tests for TrajectoryLoggerAdapter covering: - Basic trajectory creation from request logs - User message deduplication - Tool call correlation (single, parallel, orphan) - Subagent trajectory linking - Metrics tracking - Session ID management - Non-conversation request handling - Update TestRequestLogger to expose toolMetadata property - Add async wait in tests for proper event processing Co-authored-by: zhichli <57812115+zhichli@users.noreply.github.com> * rm readme * enhance trajectory export functionality with session ID mapping and folder selection * add comment * add arch * add bounding for logs and rm tests for now --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: zhichli <57812115+zhichli@users.noreply.github.com> Co-authored-by: Zhichao Li <zhichli@microsoft.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
199 lines
6.5 KiB
TypeScript
199 lines
6.5 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import type { RequestMetadata } from '@vscode/copilot-api';
|
|
import type { HTMLTracer, IChatEndpointInfo, RenderPromptResult } from '@vscode/prompt-tsx';
|
|
import type { LanguageModelToolResult2 } from 'vscode';
|
|
import { Emitter, Event } from '../../../../util/vs/base/common/event';
|
|
import { generateUuid } from '../../../../util/vs/base/common/uuid';
|
|
import { IModelAPIResponse } from '../../../endpoint/common/endpointProvider';
|
|
import { ThinkingData } from '../../../thinking/common/thinking';
|
|
import { CapturingToken } from '../../common/capturingToken';
|
|
import { AbstractRequestLogger, ILoggedRequestInfo, LoggedInfo, LoggedInfoKind, LoggedRequest, LoggedRequestKind } from '../../node/requestLogger';
|
|
|
|
/**
|
|
* A test implementation of IRequestLogger that stores logged requests for verification in tests.
|
|
* Unlike NullRequestLogger, this actually stores entries so they can be validated.
|
|
*/
|
|
export class TestRequestLogger extends AbstractRequestLogger {
|
|
private readonly _entries: LoggedInfo[] = [];
|
|
private readonly _onDidChangeRequests = new Emitter<void>();
|
|
public readonly onDidChangeRequests: Event<void> = this._onDidChangeRequests.event;
|
|
|
|
public override addPromptTrace(elementName: string, endpoint: IChatEndpointInfo, result: RenderPromptResult, trace: HTMLTracer): void {
|
|
const id = generateUuid().substring(0, 8);
|
|
this._entries.push(new TestLoggedElementInfo(id, elementName, result.tokenCount, endpoint.modelMaxPromptTokens, trace, this.currentRequest));
|
|
this._onDidChangeRequests.fire();
|
|
}
|
|
|
|
public addEntry(entry: LoggedRequest): void {
|
|
const id = generateUuid().substring(0, 8);
|
|
this._entries.push(new TestLoggedRequestInfo(id, entry, this.currentRequest));
|
|
this._onDidChangeRequests.fire();
|
|
}
|
|
|
|
public override getRequests(): LoggedInfo[] {
|
|
return [...this._entries];
|
|
}
|
|
|
|
public override logModelListCall(id: string, requestMetadata: RequestMetadata, models: IModelAPIResponse[]): void {
|
|
this.addEntry({
|
|
type: LoggedRequestKind.MarkdownContentRequest,
|
|
debugName: 'modelList',
|
|
startTimeMs: Date.now(),
|
|
icon: undefined,
|
|
markdownContent: `Model list call: ${models.length} models`,
|
|
isConversationRequest: false
|
|
});
|
|
}
|
|
|
|
public override logToolCall(id: string, name: string, args: unknown, response: LanguageModelToolResult2, thinking?: ThinkingData): void {
|
|
this._entries.push(new TestLoggedToolCall(id, name, args, response, this.currentRequest, Date.now(), thinking));
|
|
this._onDidChangeRequests.fire();
|
|
}
|
|
|
|
public override logServerToolCall(id: string, name: string, args: unknown, result: LanguageModelToolResult2): void {
|
|
this._entries.push(new TestLoggedToolCall(id, name, args, result, this.currentRequest, Date.now()));
|
|
this._onDidChangeRequests.fire();
|
|
}
|
|
|
|
/**
|
|
* Clear all logged entries (useful between tests).
|
|
*/
|
|
public clear(): void {
|
|
this._entries.length = 0;
|
|
this._onDidChangeRequests.fire();
|
|
}
|
|
}
|
|
|
|
class TestLoggedElementInfo {
|
|
public readonly kind = LoggedInfoKind.Element;
|
|
|
|
constructor(
|
|
public readonly id: string,
|
|
public readonly name: string,
|
|
public readonly tokens: number,
|
|
public readonly maxTokens: number,
|
|
public readonly trace: HTMLTracer,
|
|
public readonly token: CapturingToken | undefined
|
|
) { }
|
|
|
|
toJSON(): object {
|
|
return {
|
|
id: this.id,
|
|
kind: 'element',
|
|
name: this.name,
|
|
tokens: this.tokens,
|
|
maxTokens: this.maxTokens
|
|
};
|
|
}
|
|
}
|
|
|
|
class TestLoggedRequestInfo implements ILoggedRequestInfo {
|
|
public readonly kind = LoggedInfoKind.Request;
|
|
|
|
constructor(
|
|
public readonly id: string,
|
|
public readonly entry: LoggedRequest,
|
|
public readonly token: CapturingToken | undefined
|
|
) { }
|
|
|
|
toJSON(): object {
|
|
const baseInfo = {
|
|
id: this.id,
|
|
kind: 'request',
|
|
type: this.entry.type,
|
|
name: this.entry.debugName
|
|
};
|
|
|
|
if (this.entry.type === LoggedRequestKind.MarkdownContentRequest) {
|
|
return {
|
|
...baseInfo,
|
|
startTime: new Date(this.entry.startTimeMs).toISOString(),
|
|
content: this.entry.markdownContent
|
|
};
|
|
}
|
|
|
|
// Handle ChatML request types (Success, Failure, Cancellation)
|
|
// These all have startTime/endTime as Date objects
|
|
if (this.entry.type === LoggedRequestKind.ChatMLSuccess ||
|
|
this.entry.type === LoggedRequestKind.ChatMLFailure ||
|
|
this.entry.type === LoggedRequestKind.ChatMLCancelation) {
|
|
|
|
const metadata = {
|
|
model: this.entry.chatParams?.model,
|
|
location: this.entry.chatParams?.location,
|
|
startTime: this.entry.startTime.toISOString(),
|
|
endTime: this.entry.endTime.toISOString(),
|
|
duration: this.entry.endTime.getTime() - this.entry.startTime.getTime(),
|
|
maxResponseTokens: this.entry.chatParams?.body?.max_tokens ?? this.entry.chatParams?.body?.max_output_tokens,
|
|
};
|
|
|
|
// Build response data matching the real LoggedRequestInfo.toJSON() format
|
|
let responseData;
|
|
let errorInfo;
|
|
|
|
if (this.entry.type === LoggedRequestKind.ChatMLSuccess) {
|
|
responseData = {
|
|
type: 'success',
|
|
message: this.entry.result.value
|
|
};
|
|
} else if (this.entry.type === LoggedRequestKind.ChatMLFailure) {
|
|
errorInfo = {
|
|
type: 'failure',
|
|
reason: this.entry.result.reason
|
|
};
|
|
} else if (this.entry.type === LoggedRequestKind.ChatMLCancelation) {
|
|
errorInfo = {
|
|
type: 'canceled'
|
|
};
|
|
}
|
|
|
|
const response = responseData || errorInfo ? {
|
|
...responseData,
|
|
...errorInfo
|
|
} : undefined;
|
|
|
|
return {
|
|
...baseInfo,
|
|
metadata,
|
|
response,
|
|
isConversationRequest: this.entry.isConversationRequest
|
|
};
|
|
}
|
|
|
|
// Fallback for any unknown types
|
|
return baseInfo;
|
|
}
|
|
}
|
|
|
|
class TestLoggedToolCall {
|
|
public readonly kind = LoggedInfoKind.ToolCall;
|
|
public readonly toolMetadata: unknown;
|
|
|
|
constructor(
|
|
public readonly id: string,
|
|
public readonly name: string,
|
|
public readonly args: unknown,
|
|
public readonly response: LanguageModelToolResult2,
|
|
public readonly token: CapturingToken | undefined,
|
|
public readonly time: number,
|
|
public readonly thinking?: ThinkingData,
|
|
) {
|
|
// Extract toolMetadata from response if it exists
|
|
this.toolMetadata = 'toolMetadata' in response ? (response as { toolMetadata?: unknown }).toolMetadata : undefined;
|
|
}
|
|
|
|
async toJSON(): Promise<object> {
|
|
return {
|
|
id: this.id,
|
|
kind: 'toolCall',
|
|
tool: this.name,
|
|
args: this.args,
|
|
time: new Date(this.time).toISOString(),
|
|
};
|
|
}
|
|
}
|