MCPcopy Index your code
hub / github.com/coder/coder / executeToolsForStep

Function executeToolsForStep

coderd/x/chatd/chatloop/chatloop.go:1266–1397  ·  view source on GitHub ↗

executeToolsForStep runs the tool-execution phase of a single chatloop step. It enforces the exclusive-tool policy, partitions built-in versus dynamic tool calls, dispatches built-in tools, and when dynamic tool calls are present persists the step and returns ErrDynamicToolCall so the caller can exe

(
	ctx context.Context,
	opts RunOptions,
	result *stepResult,
	provider, modelName string,
	step int,
	stepStart time.Time,
	publishMessagePart func(codersdk.ChatMessageRole, codersdk.ChatMessagePart),
)

Source from the content-addressed store, hash-verified

1264// caller must propagate (ErrInterrupted, ErrDynamicToolCall, ctx.Err(),
1265// or a persistence failure).
1266func executeToolsForStep(
1267 ctx context.Context,
1268 opts RunOptions,
1269 result *stepResult,
1270 provider, modelName string,
1271 step int,
1272 stepStart time.Time,
1273 publishMessagePart func(codersdk.ChatMessageRole, codersdk.ChatMessagePart),
1274) ([]fantasy.ToolResultContent, error) {
1275 // Check for context cancellation before starting tool
1276 // execution. If the chat was interrupted between stream
1277 // completion and here, persist what we have and bail out.
1278 if ctx.Err() != nil {
1279 if errors.Is(context.Cause(ctx), ErrInterrupted) {
1280 persistInterruptedStep(ctx, opts, result)
1281 return nil, ErrInterrupted
1282 }
1283 return nil, ctx.Err()
1284 }
1285
1286 // Enforce exclusivity across ALL locally-executable tool
1287 // calls (both built-in and dynamic) before partitioning.
1288 // Checking only the built-in partition would let the model
1289 // bypass the policy by mixing an exclusive tool with a
1290 // dynamic tool: the exclusive tool would still run and the
1291 // dynamic call would still be handed to the caller for
1292 // external execution, breaking the planning-only contract.
1293 localCandidates := make([]fantasy.ToolCallContent, 0, len(result.toolCalls))
1294 for _, tc := range result.toolCalls {
1295 if !tc.ProviderExecuted {
1296 localCandidates = append(localCandidates, tc)
1297 }
1298 }
1299 policyResults, exclusiveViolation := applyExclusiveToolPolicy(
1300 localCandidates,
1301 opts.ExclusiveToolNames,
1302 opts.Metrics,
1303 provider,
1304 modelName,
1305 )
1306 if exclusiveViolation {
1307 now := dbtime.Now()
1308 for _, tr := range policyResults {
1309 recordToolResultTimestamp(result, tr.ToolCallID, now)
1310 publishToolAttachments(ctx, opts.Logger, tr, now, publishMessagePart)
1311 ssePart := chatprompt.PartFromContentWithLogger(ctx, opts.Logger, tr)
1312 ssePart.CreatedAt = &now
1313 publishMessagePart(codersdk.ChatMessageRoleTool, ssePart)
1314 }
1315 for _, tr := range policyResults {
1316 result.content = append(result.content, tr)
1317 }
1318 // Mirror the post-execution interruption check used by the
1319 // non-policy path: if the chat was interrupted while we
1320 // synthesized policy errors, route through
1321 // persistInterruptedStep so the synthesized results are not
1322 // dropped when the regular PersistStep path fails on a
1323 // canceled context.

Callers 1

RunFunction · 0.85

Calls 12

NowFunction · 0.92
persistInterruptedStepFunction · 0.85
applyExclusiveToolPolicyFunction · 0.85
publishToolAttachmentsFunction · 0.85
executeToolsFunction · 0.85
tryCompactOnExitFunction · 0.85
ErrMethod · 0.80
IsMethod · 0.45

Tested by

no test coverage detected