Files
coder/coderd/x/chatd/chatstate/toolresults.go
T
Michael Suchacz 91c7232d97 feat: add chat suffix messages, idle failure, and content update support (#27428)
Adds generic chat state and query capabilities that the lifecycle hooks
integration (#27429) builds on. Part of the lifecycle hooks stack
(#27401, #27429, #27430).

- `chatstate`: `EditMessage` accepts caller-provided suffix messages
inserted after the replacement in the same transaction, transitions can
carry a typed error kind, and `FinishError` is also allowed from waiting
chats so admission-time failures can park an idle chat in error.
- `chatstate`: `ValidateToolResults` holds the submitted-tool-result
rules (duplicate, invalid JSON, missing, unexpected) in one place, so
`CompleteRequiresAction` and API-level prechecks reject the same
payloads with the same typed causes.
- `database`: `InsertChat` accepts an optional caller-provided ID.

No hook-specific state or behavior is introduced here; these primitives
are usable by any caller.

An earlier revision added a message-content rewrite primitive so a
`pre_tool_use` override could update an already-committed tool call.
Message content is immutable by design, and @hugodutka pushed back on
changing that. The rewrite is gone: #27429 now dispatches the hook
before the assistant message is stored, so the stored input is the one
that runs and nothing needs updating.

> This PR was written by Mux, an AI coding agent, on Mike's behalf.
2026-07-29 13:16:29 +02:00

30 lines
1.0 KiB
Go

package chatstate
import "encoding/json"
// ValidateToolResults returns the first validation error for results
// submitted against pending dynamic tool calls.
func ValidateToolResults(results []ToolResultInput, pending map[string]string) *ToolResultValidationError {
submitted := make(map[string]struct{}, len(results))
for _, result := range results {
if _, dup := submitted[result.ToolCallID]; dup {
return &ToolResultValidationError{Cause: ErrToolResultDuplicate, ToolCallID: result.ToolCallID}
}
if !json.Valid(result.Output) {
return &ToolResultValidationError{Cause: ErrToolResultInvalidJSON, ToolCallID: result.ToolCallID}
}
submitted[result.ToolCallID] = struct{}{}
}
for toolCallID := range pending {
if _, ok := submitted[toolCallID]; !ok {
return &ToolResultValidationError{Cause: ErrToolResultMissing, ToolCallID: toolCallID}
}
}
for toolCallID := range submitted {
if _, ok := pending[toolCallID]; !ok {
return &ToolResultValidationError{Cause: ErrToolResultUnexpected, ToolCallID: toolCallID}
}
}
return nil
}