MCP tool-use execution loop for autonomous agent runs

Autonomous agents with MCP tools now run a bounded tool-use loop: the model may
call tools (executed via the gateway, results fed back) until it returns a final
answer. Gated/DraftOnly agents get the tool catalog as data but never auto-call —
a human-in-the-loop agent never autonomously reaches an external tool.

Extends IModelClient with tool definitions and a tool-use conversation, adds the
OpenAI-compatible tool serialization/parsing plus a deterministic "tooluse" stub
client, and records every tool call in the run trace.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-15 15:20:48 +03:30
parent a9d4d691f0
commit c8d9af6191
5 changed files with 285 additions and 16 deletions
@@ -1,15 +1,42 @@
namespace TeamUp.SharedKernel.Ai;
/// <summary>One model invocation. The key is passed explicitly (BYOK, server-side only).</summary>
/// <summary>A tool the model may call (OpenAI "function" tool). Parameters is a JSON-Schema string.</summary>
public sealed record ModelTool(string Name, string? Description, string ParametersJson);
/// <summary>A tool call the model asked for, to be executed and fed back.</summary>
public sealed record ModelToolCall(string Id, string Name, string ArgumentsJson);
/// <summary>
/// One message in a tool-use conversation. Role is user|assistant|tool. An assistant turn may carry
/// <see cref="ToolCalls"/>; a tool turn carries the result <see cref="Content"/> for <see cref="ToolCallId"/>.
/// </summary>
public sealed record ModelMessage(
string Role,
string? Content,
IReadOnlyList<ModelToolCall>? ToolCalls = null,
string? ToolCallId = null);
/// <summary>
/// One model invocation. The key is passed explicitly (BYOK, server-side only). When
/// <see cref="Messages"/> is set it is the full conversation (for the tool-use loop) and overrides
/// <see cref="Prompt"/>; <see cref="Tools"/> offers callable tools.
/// </summary>
public sealed record ModelRequest(
string Provider,
string Model,
string ApiKey,
string? Endpoint,
string Prompt,
int MaxTokens = 256);
int MaxTokens = 256,
IReadOnlyList<ModelTool>? Tools = null,
IReadOnlyList<ModelMessage>? Messages = null);
public sealed record ModelCompletion(bool Success, string? Text, string? Error, long LatencyMs);
public sealed record ModelCompletion(
bool Success,
string? Text,
string? Error,
long LatencyMs,
IReadOnlyList<ModelToolCall>? ToolCalls = null);
/// <summary>
/// Provider-agnostic model client. Implemented in Integrations (a router over per-provider HTTP