(raw json.RawMessage, fileMeta *models.QDevS3FileMeta, identityClient UserDisplayNameResolver, cache *sync.Map)
| 309 | } |
| 310 | |
| 311 | func parseChatRecord(raw json.RawMessage, fileMeta *models.QDevS3FileMeta, identityClient UserDisplayNameResolver, cache *sync.Map) (*models.QDevChatLog, error) { |
| 312 | var record chatLogRecord |
| 313 | if err := json.Unmarshal(raw, &record); err != nil { |
| 314 | return nil, err |
| 315 | } |
| 316 | |
| 317 | if record.Request == nil || record.Response == nil { |
| 318 | return nil, nil |
| 319 | } |
| 320 | |
| 321 | ts, err := time.Parse(time.RFC3339Nano, record.Request.Timestamp) |
| 322 | if err != nil { |
| 323 | ts = time.Now() |
| 324 | } |
| 325 | |
| 326 | userId := normalizeUserId(record.Request.UserID) |
| 327 | chatLog := &models.QDevChatLog{ |
| 328 | ConnectionId: fileMeta.ConnectionId, |
| 329 | ScopeId: fileMeta.ScopeId, |
| 330 | RequestId: record.Response.RequestID, |
| 331 | UserId: userId, |
| 332 | DisplayName: cachedResolveDisplayName(userId, identityClient, cache), |
| 333 | Timestamp: ts, |
| 334 | ChatTriggerType: record.Request.ChatTriggerType, |
| 335 | HasCustomization: record.Request.CustomizationArn != nil && *record.Request.CustomizationArn != "", |
| 336 | ModelId: record.Request.ModelID, |
| 337 | PromptLength: len(record.Request.Prompt), |
| 338 | ResponseLength: len(record.Response.AssistantResponse), |
| 339 | } |
| 340 | |
| 341 | // Parse structured info from prompt |
| 342 | prompt := record.Request.Prompt |
| 343 | chatLog.OpenFileCount = countOpenFiles(prompt) |
| 344 | chatLog.ActiveFileName, chatLog.ActiveFileExtension = parseActiveFile(prompt) |
| 345 | chatLog.HasSteering = strings.Contains(prompt, ".kiro/steering") |
| 346 | chatLog.IsSpecMode = strings.Contains(prompt, "implicit-rules") |
| 347 | |
| 348 | if record.Response.MessageMetadata.ConversationID != nil { |
| 349 | chatLog.ConversationId = *record.Response.MessageMetadata.ConversationID |
| 350 | } |
| 351 | if record.Response.MessageMetadata.UtteranceID != nil { |
| 352 | chatLog.UtteranceId = *record.Response.MessageMetadata.UtteranceID |
| 353 | } |
| 354 | |
| 355 | // New fields from docs: codeReferenceEvents, supplementaryWebLinksEvent, followupPrompts |
| 356 | chatLog.CodeReferenceCount = len(record.Response.CodeReferenceEvents) |
| 357 | chatLog.WebLinkCount = len(record.Response.SupplementaryWebLinksEvent) |
| 358 | chatLog.HasFollowupPrompts = record.Response.FollowupPrompts != "" |
| 359 | |
| 360 | return chatLog, nil |
| 361 | } |
| 362 | |
| 363 | // countOpenFiles counts <file name="..."> tags within <OPEN-EDITOR-FILES> block |
| 364 | func countOpenFiles(prompt string) int { |
no test coverage detected