refactor(coderd/x/chatd): return PersistedStep directly from ExecuteLocalTools (#28359)

**Stack**: **#28359 (this PR)** → #28360 → #28361 → #28362

`chatloop.ToolExecutionOutcome` only wrapped a `PersistedStep`, so
callers had to reach through `outcome.Step` for every field.
`ExecuteLocalTools` now returns the `PersistedStep` directly and the
wrapper type is deleted. No behavior change.

First of a four-PR stack that makes local tool execution count toward
Coder Agent runtime (splitting #28211). This refactor keeps the rename
noise out of the feature PRs.

Refs CODAGT-928
(https://linear.app/codercom/issue/CODAGT-928/track-local-tool-execution-for-agent-runtime).
This commit is contained in:
Jaayden Halko
2026-08-25 16:31:06 +07:00
committed by GitHub
parent e98e2b9b2e
commit d44aae37de
2 changed files with 16 additions and 20 deletions
+9 -14
View File
@@ -275,11 +275,6 @@ type ExecuteLocalToolsOptions struct {
Clock quartz.Clock
}
// ToolExecutionOutcome is the durable tool-result content from one batch.
type ToolExecutionOutcome struct {
Step PersistedStep
}
// GenerateCompactionOptions configures one context compaction call.
type GenerateCompactionOptions struct {
Model fantasy.LanguageModel
@@ -562,7 +557,7 @@ func contentFilterError(provider string, metadata fantasy.ProviderMetadata) erro
// ExecuteLocalTools runs local tool calls and returns durable tool results. It
// does not retry or persist.
func ExecuteLocalTools(ctx context.Context, opts ExecuteLocalToolsOptions) (ToolExecutionOutcome, error) {
func ExecuteLocalTools(ctx context.Context, opts ExecuteLocalToolsOptions) (PersistedStep, error) {
if opts.Metrics == nil {
opts.Metrics = NopMetrics()
}
@@ -584,7 +579,7 @@ func ExecuteLocalTools(ctx context.Context, opts ExecuteLocalToolsOptions) (Tool
// without capturing the publisher at construction time.
ctx = WithMessagePartPublisher(ctx, opts.PublishMessagePart)
if ctx.Err() != nil {
return ToolExecutionOutcome{}, ctx.Err()
return PersistedStep{}, ctx.Err()
}
localCalls := make([]fantasy.ToolCallContent, 0, len(opts.ToolCalls))
@@ -594,7 +589,7 @@ func ExecuteLocalTools(ctx context.Context, opts ExecuteLocalToolsOptions) (Tool
}
}
if len(localCalls) == 0 {
return ToolExecutionOutcome{}, nil
return PersistedStep{}, nil
}
var result stepResult
@@ -616,12 +611,12 @@ func ExecuteLocalTools(ctx context.Context, opts ExecuteLocalToolsOptions) (Tool
result.content = append(result.content, tr)
}
if ctx.Err() != nil {
return ToolExecutionOutcome{}, ctx.Err()
return PersistedStep{}, ctx.Err()
}
return ToolExecutionOutcome{Step: PersistedStep{
return PersistedStep{
Content: result.content,
ToolResultCreatedAt: result.toolResultCreatedAt,
}}, nil
}, nil
}
maxResultBytes := toolResultByteBudget(opts.ContextLimit)
@@ -650,15 +645,15 @@ func ExecuteLocalTools(ctx context.Context, opts ExecuteLocalToolsOptions) (Tool
},
)
if ctx.Err() != nil {
return ToolExecutionOutcome{}, ctx.Err()
return PersistedStep{}, ctx.Err()
}
for _, tr := range toolResults {
result.content = append(result.content, tr)
}
return ToolExecutionOutcome{Step: PersistedStep{
return PersistedStep{
Content: result.content,
ToolResultCreatedAt: result.toolResultCreatedAt,
}}, nil
}, nil
}
// prepareMessagesForRequest applies the prompt preparation pipeline used
+7 -6
View File
@@ -858,7 +858,7 @@ func (s *taskStarter) executeLocalTools(
provider = prepared.Model.Provider()
modelName = prepared.Model.ModelID()
}
var outcome chatloop.ToolExecutionOutcome
var outcome chatloop.PersistedStep
var spawnDispatchErr error
if len(allowed) > 0 {
outcome, err = chatloop.ExecuteLocalTools(ctx, chatloop.ExecuteLocalToolsOptions{
@@ -886,18 +886,19 @@ func (s *taskStarter) executeLocalTools(
// the tool run; its failure surfaces as a tool result error. The
// step still commits so a sibling tool that already ran keeps its
// result and is not re-executed, and the turn fails afterwards.
if hookErr := chathooks.DispatchFailureFromResults(outcome.Step.Content); hookErr != nil {
if hookErr := chathooks.DispatchFailureFromResults(outcome.Content); hookErr != nil {
spawnDispatchErr = chathooks.GenerationDispatchError(agenthooks.EventUserPromptSubmit, hookErr)
}
}
postResults, postDispatchErr := s.server.hooks.PostToolUseResults(ctx, chathooks.ChatFor(prepared.Chat, input.hookTurnID()), outcome.Step.Content)
postResults, postDispatchErr := s.server.hooks.PostToolUseResults(ctx, chathooks.ChatFor(prepared.Chat, input.hookTurnID()), outcome.Content)
for _, result := range denied {
outcome.Step.Content = append(outcome.Step.Content, result)
outcome.Content = append(outcome.Content, result)
}
chathooks.RestoreToolCallOrder(outcome.Step.Content, decision.localToolCalls)
chathooks.RestoreToolCallOrder(outcome.Content, decision.localToolCalls)
step := stepDataFromPersisted(outcome)
messages, err := buildCommitStepMessages(buildCommitStepMessagesInput{
modelConfigID: prepared.ModelConfigID,
step: stepDataFromPersisted(outcome.Step),
step: step,
toolNameToConfigID: prepared.ToolNameToConfigID,
logger: s.opts.Logger,
contentVersion: chatprompt.CurrentContentVersion,