insertSyntheticToolResultsTx inserts IsError tool-result messages for unresolved dynamic tool calls in the last assistant message, skipping calls already handled (e.g. by chatloop dispatching a name-colliding dynamic tool as a built-in). It operates on the provided store, which may be a transaction
( ctx context.Context, store database.Store, chat database.Chat, reason string, )
| 9461 | // name-colliding dynamic tool as a built-in). It operates on the |
| 9462 | // provided store, which may be a transaction handle. |
| 9463 | func insertSyntheticToolResultsTx( |
| 9464 | ctx context.Context, |
| 9465 | store database.Store, |
| 9466 | chat database.Chat, |
| 9467 | reason string, |
| 9468 | ) ([]database.ChatMessage, error) { |
| 9469 | dynamicToolNames, err := parseDynamicToolNames(chat.DynamicTools) |
| 9470 | if err != nil { |
| 9471 | return nil, xerrors.Errorf("parse dynamic tools: %w", err) |
| 9472 | } |
| 9473 | if len(dynamicToolNames) == 0 { |
| 9474 | return nil, nil |
| 9475 | } |
| 9476 | |
| 9477 | // No assistant means nothing to close: a deferred promote can |
| 9478 | // race a worker that fails before any persist, and the cleanup |
| 9479 | // TX must still advance. |
| 9480 | lastAssistant, err := store.GetLastChatMessageByRole(ctx, database.GetLastChatMessageByRoleParams{ |
| 9481 | ChatID: chat.ID, |
| 9482 | Role: database.ChatMessageRoleAssistant, |
| 9483 | }) |
| 9484 | if errors.Is(err, sql.ErrNoRows) { |
| 9485 | return nil, nil |
| 9486 | } |
| 9487 | if err != nil { |
| 9488 | return nil, xerrors.Errorf("get last assistant message: %w", err) |
| 9489 | } |
| 9490 | |
| 9491 | parts, err := chatprompt.ParseContent(lastAssistant) |
| 9492 | if err != nil { |
| 9493 | return nil, xerrors.Errorf("parse assistant message: %w", err) |
| 9494 | } |
| 9495 | |
| 9496 | // Mirrors SubmitToolResults. |
| 9497 | afterMsgs, err := store.GetChatMessagesByChatID(ctx, database.GetChatMessagesByChatIDParams{ |
| 9498 | ChatID: chat.ID, |
| 9499 | AfterID: lastAssistant.ID, |
| 9500 | }) |
| 9501 | if err != nil { |
| 9502 | return nil, xerrors.Errorf("get messages after assistant: %w", err) |
| 9503 | } |
| 9504 | handledCallIDs := make(map[string]bool) |
| 9505 | for _, msg := range afterMsgs { |
| 9506 | if msg.Role != database.ChatMessageRoleTool { |
| 9507 | continue |
| 9508 | } |
| 9509 | msgParts, err := chatprompt.ParseContent(msg) |
| 9510 | if err != nil { |
| 9511 | continue |
| 9512 | } |
| 9513 | for _, mp := range msgParts { |
| 9514 | if mp.Type == codersdk.ChatMessagePartTypeToolResult { |
| 9515 | handledCallIDs[mp.ToolCallID] = true |
| 9516 | } |
| 9517 | } |
| 9518 | } |
| 9519 | |
| 9520 | // Collect dynamic tool calls that need synthetic results. |