@@ -35,7 +35,7 @@ import { createConnectorGeneratorTool, CONNECTOR_GENERATOR_TOOL } from "../libs/
3535import { LangfuseExporter } from 'langfuse-vercel' ;
3636import { NodeSDK } from '@opentelemetry/sdk-node' ;
3737import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node' ;
38- import { sendTelemetryEvent , TM_EVENT_BALLERINA_AI_QUERY_SUBMIT , TM_EVENT_BALLERINA_AI_GENERATION_FINISHED , CMP_BALLERINA_AI } from "../../../telemetry" ;
38+ import { sendTelemetryEvent , sendTelemetryException , TM_EVENT_BALLERINA_AI_QUERY_SUBMIT , TM_EVENT_BALLERINA_AI_GENERATION_COMPLETED , TM_EVENT_BALLERINA_AI_GENERATION_ERROR , TM_EVENT_BALLERINA_AI_GENERATION_ABORTED , TM_EVENT_BALLERINA_AI_DIAGNOSTICS , CMP_BALLERINA_AI } from "../../../telemetry" ;
3939import { extension } from "../../../../BalExtensionContext" ;
4040
4141const LANGFUSE_SECRET = process . env . LANGFUSE_SECRET ;
@@ -64,11 +64,17 @@ export async function generateDesignCore(params: GenerateAgentCodeRequest, event
6464
6565 const cacheOptions = await getProviderCacheControl ( ) ;
6666
67+ // Get state machine context for telemetry
68+ const stateContext = AIChatStateMachine . context ( ) ;
69+
6770 // Send telemetry when the user submits a query
6871 sendTelemetryEvent ( extension . ballerinaExtInstance , TM_EVENT_BALLERINA_AI_QUERY_SUBMIT , CMP_BALLERINA_AI , {
72+ projectId : stateContext . projectId || 'unknown' ,
6973 messageId : messageId ,
7074 command : Command . Design ,
7175 operationType : params . operationType ,
76+ isPlanMode : isPlanModeEnabled . toString ( ) ,
77+ approvalMode : stateContext . autoApproveEnabled ? 'auto' : 'manual' ,
7278 } ) ;
7379
7480 const modifiedFiles : string [ ] = [ ] ;
@@ -127,15 +133,36 @@ export async function generateDesignCore(params: GenerateAgentCodeRequest, event
127133 let accumulatedMessages : any [ ] = [ ] ;
128134 let currentAssistantContent : any [ ] = [ ] ;
129135
136+ // Timing metrics for telemetry
137+ let generationStartTime : number | undefined ;
138+ let firstTokenTime : number | undefined ;
139+ let lastTokenTime : number | undefined ;
140+
130141 for await ( const part of fullStream ) {
131142 switch ( part . type ) {
132143 case "text-delta" : {
144+ // Capture first token time
145+ if ( ! firstTokenTime ) {
146+ firstTokenTime = Date . now ( ) ;
147+ generationStartTime = firstTokenTime ;
148+ }
149+ // Update last token time
150+ lastTokenTime = Date . now ( ) ;
151+
133152 const textPart = part . text ;
134153 eventHandler ( { type : "content_block" , content : textPart } ) ;
135154 accumulateTextContent ( currentAssistantContent , textPart ) ;
136155 break ;
137156 }
138157 case "tool-call" : {
158+ // Capture first token time for tool calls as well
159+ if ( ! firstTokenTime ) {
160+ firstTokenTime = Date . now ( ) ;
161+ generationStartTime = firstTokenTime ;
162+ }
163+ // Update last token time
164+ lastTokenTime = Date . now ( ) ;
165+
139166 const toolName = part . toolName ;
140167 accumulateToolCall ( currentAssistantContent , part ) ;
141168
@@ -207,6 +234,20 @@ export async function generateDesignCore(params: GenerateAgentCodeRequest, event
207234 toolOutput : { success : true , action }
208235 } ) ;
209236 } else if ( toolName === DIAGNOSTICS_TOOL_NAME ) {
237+ // Send telemetry for diagnostics
238+ const diagnosticsResult = result as any ;
239+ const hasErrors = diagnosticsResult ?. diagnostics ?. some ( ( d : any ) => d . severity === 'Error' ) || false ;
240+ const errorCount = diagnosticsResult ?. diagnostics ?. filter ( ( d : any ) => d . severity === 'Error' ) . length || 0 ;
241+ const warningCount = diagnosticsResult ?. diagnostics ?. filter ( ( d : any ) => d . severity === 'Warning' ) . length || 0 ;
242+
243+ sendTelemetryEvent ( extension . ballerinaExtInstance , TM_EVENT_BALLERINA_AI_DIAGNOSTICS , CMP_BALLERINA_AI , {
244+ projectId : stateContext . projectId || 'unknown' ,
245+ messageId : messageId ,
246+ hasErrors : hasErrors . toString ( ) ,
247+ errorCount : errorCount . toString ( ) ,
248+ warningCount : warningCount . toString ( ) ,
249+ } ) ;
250+
210251 eventHandler ( {
211252 type : "tool_result" ,
212253 toolName,
@@ -220,8 +261,26 @@ export async function generateDesignCore(params: GenerateAgentCodeRequest, event
220261 case "error" : {
221262 const error = part . error ;
222263 console . error ( "[Design] Error:" , error ) ;
264+
265+ // Send telemetry for generation error
266+ const errorObj = error instanceof Error ? error : new Error ( String ( error ) ) ;
267+ sendTelemetryEvent ( extension . ballerinaExtInstance , TM_EVENT_BALLERINA_AI_GENERATION_ERROR , CMP_BALLERINA_AI , {
268+ projectId : stateContext . projectId || 'unknown' ,
269+ messageId : messageId ,
270+ errorMessage : getErrorMessage ( error ) ,
271+ errorType : errorObj . name || 'Unknown' ,
272+ generationStartTime : generationStartTime ?. toString ( ) || 'unknown' ,
273+ errorTime : Date . now ( ) . toString ( ) ,
274+ } ) ;
275+
276+ sendTelemetryException ( extension . ballerinaExtInstance , errorObj , CMP_BALLERINA_AI , {
277+ projectId : stateContext . projectId || 'unknown' ,
278+ messageId : messageId ,
279+ } ) ;
280+
223281 // Cleanup temp project on error
224282 cleanupTempProject ( tempProjectPath ) ;
283+
225284 eventHandler ( { type : "error" , content : getErrorMessage ( error ) } ) ;
226285 break ;
227286 }
@@ -231,6 +290,19 @@ export async function generateDesignCore(params: GenerateAgentCodeRequest, event
231290 }
232291 case "abort" : {
233292 console . log ( "[Design] Aborted by user." ) ;
293+ const abortTime = Date . now ( ) ;
294+
295+ // Send telemetry for generation abort
296+ sendTelemetryEvent ( extension . ballerinaExtInstance , TM_EVENT_BALLERINA_AI_GENERATION_ABORTED , CMP_BALLERINA_AI , {
297+ projectId : stateContext . projectId || 'unknown' ,
298+ messageId : messageId ,
299+ generationStartTime : generationStartTime ?. toString ( ) || 'unknown' ,
300+ abortTime : abortTime . toString ( ) ,
301+ firstTokenTime : firstTokenTime ?. toString ( ) || 'unknown' ,
302+ lastTokenTime : lastTokenTime ?. toString ( ) || 'unknown' ,
303+ modifiedFilesCount : modifiedFiles . length . toString ( ) ,
304+ } ) ;
305+
234306 let messagesToSave : any [ ] = [ ] ;
235307 try {
236308 const partialResponse = await response ;
@@ -272,6 +344,7 @@ Generation stopped by user. The last in-progress task was not saved. Files have
272344 const finishReason = part . finishReason ;
273345 const finalResponse = await response ;
274346 const assistantMessages = finalResponse . messages || [ ] ;
347+ const generationEndTime = Date . now ( ) ;
275348
276349 console . log ( `[Design] Finished with reason: ${ finishReason } ` ) ;
277350
@@ -288,11 +361,17 @@ Generation stopped by user. The last in-progress task was not saved. Files have
288361 updateAndSaveChat ( messageId , userMessageContent , assistantMessages , eventHandler ) ;
289362 eventHandler ( { type : "stop" , command : Command . Design } ) ;
290363
291- // Send telemetry after generation
292- sendTelemetryEvent ( extension . ballerinaExtInstance , TM_EVENT_BALLERINA_AI_GENERATION_FINISHED , CMP_BALLERINA_AI , {
364+ sendTelemetryEvent ( extension . ballerinaExtInstance , TM_EVENT_BALLERINA_AI_GENERATION_COMPLETED , CMP_BALLERINA_AI , {
365+ projectId : stateContext . projectId || 'unknown' ,
293366 messageId : messageId ,
294367 finishReason : finishReason ,
295368 modifiedFilesCount : modifiedFiles . length . toString ( ) ,
369+ generationStartTime : generationStartTime ?. toString ( ) || 'unknown' ,
370+ generationEndTime : generationEndTime . toString ( ) ,
371+ firstTokenTime : firstTokenTime ?. toString ( ) || 'unknown' ,
372+ lastTokenTime : lastTokenTime ?. toString ( ) || 'unknown' ,
373+ isPlanMode : isPlanModeEnabled . toString ( ) ,
374+ approvalMode : stateContext . autoApproveEnabled ? 'auto' : 'manual' ,
296375 } ) ;
297376
298377 AIChatStateMachine . sendEvent ( {
0 commit comments