Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1890,6 +1890,10 @@
"type": "string",
"enum": ["BLOCK", "LINE"]
},
"SuggestionType": {
"type": "string",
"enum": ["COMPLETIONS", "EDITS"]
},
"Completions": {
"type": "list",
"member": {
Expand Down Expand Up @@ -6299,6 +6303,12 @@
},
"ideVersion": {
"shape": "String"
},
"pluginVersion": {
"shape": "String"
},
"lspVersion": {
"shape": "String"
}
}
},
Expand Down Expand Up @@ -6540,6 +6550,9 @@
},
"streakLength": {
"shape": "UserTriggerDecisionEventStreakLengthInteger"
},
"suggestionType": {
"shape": "SuggestionType"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ declare namespace CodeWhispererBearerTokenClient {
}
export type CompletionContentString = string;
export type CompletionType = "BLOCK"|"LINE"|string;
export type SuggestionType = "COMPLETIONS"|"EDITS"|string;
export type Completions = Completion[];
export interface ConsoleState {
region?: String;
Expand Down Expand Up @@ -2014,6 +2015,8 @@ declare namespace CodeWhispererBearerTokenClient {
product: UserContextProductString;
clientId?: UUID;
ideVersion?: String;
pluginVersion?: String;
lspVersion?: String;
}
export type UserContextProductString = string;
export interface UserInputMessage {
Expand Down Expand Up @@ -2127,6 +2130,7 @@ declare namespace CodeWhispererBearerTokenClient {
addedCharacterCount?: UserTriggerDecisionEventAddedCharacterCountInteger;
deletedCharacterCount?: UserTriggerDecisionEventDeletedCharacterCountInteger;
streakLength?: UserTriggerDecisionEventStreakLengthInteger;
suggestionType?: SuggestionType;
}
export type UserTriggerDecisionEventAddedCharacterCountInteger = number;
export type UserTriggerDecisionEventDeletedCharacterCountInteger = number;
Expand Down Expand Up @@ -2182,4 +2186,4 @@ declare namespace CodeWhispererBearerTokenClient {
}
export = CodeWhispererBearerTokenClient;


Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
* Will be deleted or merged.
*/

import * as crypto from 'crypto'

Check warning on line 6 in server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts

View workflow job for this annotation

GitHub Actions / Test (Windows)

Do not import Node.js builtin module "crypto"
import * as path from 'path'

Check warning on line 7 in server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts

View workflow job for this annotation

GitHub Actions / Test (Windows)

Do not import Node.js builtin module "path"
import * as os from 'os'

Check warning on line 8 in server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts

View workflow job for this annotation

GitHub Actions / Test (Windows)

Do not import Node.js builtin module "os"
import {
ChatTriggerType,
Origin,
Expand Down Expand Up @@ -166,6 +166,7 @@
import { ExecuteBash, ExecuteBashParams } from './tools/executeBash'
import { ExplanatoryParams, InvokeOutput, ToolApprovalException } from './tools/toolShared'
import { validatePathBasic, validatePathExists, validatePaths as validatePathsSync } from './utils/pathValidation'
import { calculateModifiedLines } from './utils/fileModificationMetrics'
import { GrepSearch, SanitizedRipgrepOutput } from './tools/grepSearch'
import { FileSearch, FileSearchParams, isFileSearchParams } from './tools/fileSearch'
import { FsReplace, FsReplaceParams } from './tools/fsReplace'
Expand Down Expand Up @@ -2076,6 +2077,17 @@
this.#abTestingAllocation?.experimentName,
this.#abTestingAllocation?.userVariation
)
// Emit acceptedLineCount when write tool is used and code changes are accepted
const acceptedLineCount = calculateModifiedLines(toolUse, doc?.getText())
await this.#telemetryController.emitInteractWithMessageMetric(
tabId,
{
cwsprChatMessageId: chatResult.messageId ?? toolUse.toolUseId,
cwsprChatInteractionType: ChatInteractionType.AgenticCodeAccepted,
codewhispererCustomizationArn: this.#customizationArn,
},
acceptedLineCount
)
await chatResultStream.writeResultBlock(chatResult)
break
case CodeReview.toolName:
Expand Down Expand Up @@ -2694,6 +2706,7 @@
session.setDeferredToolExecution(messageId, deferred.resolve, deferred.reject)
this.#log(`Prompting for compaction approval for messageId: ${messageId}`)
await deferred.promise
session.removeDeferredToolExecution(messageId)
// Note: we want to overwrite the button block because it already exists in the stream.
await resultStream.overwriteResultBlock(this.#getUpdateCompactConfirmResult(messageId), promptBlockId)
}
Expand Down Expand Up @@ -3358,7 +3371,7 @@
metric: Metric<CombinedConversationEvent>,
agenticCodingMode: boolean
): Promise<ChatResult | ResponseError<ChatResult>> {
const errorMessage = getErrorMsg(err)
const errorMessage = getErrorMsg(err) ?? GENERIC_ERROR_MS
const requestID = getRequestID(err) ?? ''
metric.setDimension('cwsprChatResponseCode', getHttpStatusCode(err) ?? 0)
metric.setDimension('languageServerVersion', this.#features.runtime.serverInfo.version)
Expand All @@ -3368,7 +3381,7 @@
metric.metric.requestIds = [requestID]
metric.metric.cwsprChatMessageId = errorMessageId
metric.metric.cwsprChatConversationId = conversationId
await this.#telemetryController.emitAddMessageMetric(tabId, metric.metric, 'Failed')
await this.#telemetryController.emitAddMessageMetric(tabId, metric.metric, 'Failed', errorMessage)

if (isUsageLimitError(err)) {
if (this.#paidTierMode !== 'paidtier') {
Expand Down Expand Up @@ -3413,7 +3426,7 @@
tabId,
metric.metric,
requestID,
errorMessage ?? GENERIC_ERROR_MS,
errorMessage,
agenticCodingMode
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { calculateModifiedLines } from './fileModificationMetrics'
import { ToolUse } from '@amzn/codewhisperer-streaming'
import { FS_WRITE, FS_REPLACE } from '../constants/toolConstants'
import * as assert from 'assert'

describe('calculateModifiedLines', () => {
describe('FS_WRITE', () => {
it('should count lines for create command', () => {
const toolUse: ToolUse = {
toolUseId: 'test-1',
name: FS_WRITE,
input: {
command: 'create',
path: '/test/file.txt',
fileText: 'line1\nline2\nline3',
},
}
const afterContent = 'line1\nline2\nline3'

assert.strictEqual(calculateModifiedLines(toolUse, afterContent), 3)
})

it('should count lines for append command', () => {
const toolUse: ToolUse = {
toolUseId: 'test-2',
name: FS_WRITE,
input: {
command: 'append',
path: '/test/file.txt',
fileText: 'line4\nline5',
},
}

assert.strictEqual(calculateModifiedLines(toolUse), 2)
})

it('should handle empty content', () => {
const toolUse: ToolUse = {
toolUseId: 'test-3',
name: FS_WRITE,
input: {
command: 'create',
path: '/test/file.txt',
fileText: '',
},
}

assert.strictEqual(calculateModifiedLines(toolUse, ''), 0)
})
})

describe('FS_REPLACE', () => {
it('should count replaced lines correctly (double counting)', () => {
const toolUse: ToolUse = {
toolUseId: 'test-4',
name: FS_REPLACE,
input: {
path: '/test/file.txt',
diffs: [
{
oldStr: 'old line 1\nold line 2\nold line 3',
newStr: 'new line 1\nnew line 2\nnew line 3',
},
],
},
}

assert.strictEqual(calculateModifiedLines(toolUse), 6)
})

it('should count pure deletions', () => {
const toolUse: ToolUse = {
toolUseId: 'test-5',
name: FS_REPLACE,
input: {
path: '/test/file.txt',
diffs: [
{
oldStr: 'line to delete 1\nline to delete 2',
newStr: '',
},
],
},
}

assert.strictEqual(calculateModifiedLines(toolUse), 2)
})

it('should count pure insertions', () => {
const toolUse: ToolUse = {
toolUseId: 'test-6',
name: FS_REPLACE,
input: {
path: '/test/file.txt',
diffs: [
{
oldStr: '',
newStr: 'new line 1\nnew line 2',
},
],
},
}

assert.strictEqual(calculateModifiedLines(toolUse), 2)
})

it('should handle multiple diffs', () => {
const toolUse: ToolUse = {
toolUseId: 'test-7',
name: FS_REPLACE,
input: {
path: '/test/file.txt',
diffs: [
{
oldStr: 'old line 1',
newStr: 'new line 1',
},
{
oldStr: 'delete this line',
newStr: '',
},
],
},
}

assert.strictEqual(calculateModifiedLines(toolUse), 3)
})
})

it('should return 0 for unknown tools', () => {
const toolUse: ToolUse = {
toolUseId: 'test-8',
name: 'unknownTool',
input: {},
}

assert.strictEqual(calculateModifiedLines(toolUse), 0)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { ToolUse } from '@amzn/codewhisperer-streaming'
import { diffLines } from 'diff'
import { FsWriteParams } from '../tools/fsWrite'
import { FsReplaceParams } from '../tools/fsReplace'
import { FS_WRITE, FS_REPLACE } from '../constants/toolConstants'

/**
* Counts the number of lines in text, handling different line endings
* @param text The text to count lines in
* @returns The number of lines
*/
function countLines(text?: string): number {
if (!text) return 0
const parts = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n')
return parts.length && parts[parts.length - 1] === '' ? parts.length - 1 : parts.length
}

/**
* Calculates the actual lines modified by analyzing file modification tools.
* @param toolUse The tool use object
* @param afterContent The content after the tool execution (for FS_WRITE create operations)
* @returns The total number of lines modified (added + removed)
*/
export function calculateModifiedLines(toolUse: ToolUse, afterContent?: string): number {
if (toolUse.name === FS_WRITE) {
const input = toolUse.input as unknown as FsWriteParams

if (input.command === 'create') {
return countLines(afterContent ?? '')
} else if (input.command === 'append') {
return countLines(input.fileText)
}
}

if (toolUse.name === FS_REPLACE) {
const input = toolUse.input as unknown as FsReplaceParams
let linesAdded = 0
let linesRemoved = 0

for (const diff of input.diffs || []) {
const oldStr = diff.oldStr ?? ''
const newStr = diff.newStr ?? ''

const changes = diffLines(oldStr, newStr)

for (const change of changes) {
if (change.added) {
linesAdded += countLines(change.value)
} else if (change.removed) {
linesRemoved += countLines(change.value)
}
}
}

return linesAdded + linesRemoved
}
return 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,17 @@ export class ChatSessionService {
public getDeferredToolExecution(messageId: string): DeferredHandler | undefined {
return this.#deferredToolExecution[messageId]
}

public setDeferredToolExecution(messageId: string, resolve: any, reject: any) {
this.#deferredToolExecution[messageId] = { resolve, reject }
}

public removeDeferredToolExecution(messageId: string) {
if (messageId in this.#deferredToolExecution) {
delete this.#deferredToolExecution[messageId]
}
}

public getAllDeferredCompactMessageIds(): string[] {
return Object.keys(this.#deferredToolExecution).filter(messageId => messageId.endsWith('_compact'))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,12 @@ export class ChatTelemetryController {
})
}

public emitAddMessageMetric(tabId: string, metric: Partial<CombinedConversationEvent>, result?: string) {
public emitAddMessageMetric(
tabId: string,
metric: Partial<CombinedConversationEvent>,
result?: string,
errorMessage?: string
) {
const conversationId = this.getConversationId(tabId)
// Store the customization value associated with the message
if (metric.cwsprChatMessageId && metric.codewhispererCustomizationArn) {
Expand Down Expand Up @@ -349,6 +354,7 @@ export class ChatTelemetryController {
requestIds: metric.requestIds,
experimentName: metric.experimentName,
userVariation: metric.userVariation,
errorMessage: errorMessage,
}
)
}
Expand Down Expand Up @@ -421,10 +427,12 @@ export class ChatTelemetryController {

public emitInteractWithMessageMetric(
tabId: string,
metric: Omit<InteractWithMessageEvent, 'cwsprChatConversationId'>
metric: Omit<InteractWithMessageEvent, 'cwsprChatConversationId'>,
acceptedLineCount?: number
) {
return this.#telemetryService.emitChatInteractWithMessage(metric, {
conversationId: this.getConversationId(tabId),
acceptedLineCount,
})
}

Expand Down
Loading
Loading