From f63d402e0da324dbaf543172f5962364b09dc563 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 18:53:52 +0000 Subject: [PATCH 1/2] Initial plan From 2337ee46fc79c4e7c977604c6d1da8f492689d09 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 18:59:08 +0000 Subject: [PATCH 2/2] Set ToolCallId from FunctionResultContent.CallId when creating tool messages Co-authored-by: kzu <169707+kzu@users.noreply.github.com> --- src/xAI.Tests/ChatClientTests.cs | 77 ++++++++++++++++++++++++++++++++ src/xAI/GrokChatClient.cs | 7 ++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/xAI.Tests/ChatClientTests.cs b/src/xAI.Tests/ChatClientTests.cs index 3e537cf..12b0142 100644 --- a/src/xAI.Tests/ChatClientTests.cs +++ b/src/xAI.Tests/ChatClientTests.cs @@ -2,6 +2,7 @@ using System.Text.Json.Nodes; using Azure; using Devlooped.Extensions.AI; +using Grpc.Core; using Microsoft.Extensions.AI; using Moq; using Tests.Client.Helpers; @@ -520,5 +521,81 @@ public async Task AskFiles() } + [Fact] + public async Task GrokSetsToolCallIdFromFunctionResultContent() + { + GetCompletionsRequest? capturedRequest = null; + var client = new Mock(MockBehavior.Strict); + client.Setup(x => x.GetCompletionAsync(It.IsAny(), null, null, CancellationToken.None)) + .Callback((req, _, _, _) => capturedRequest = req) + .Returns(CallHelpers.CreateAsyncUnaryCall(new GetChatCompletionResponse + { + Outputs = + { + new CompletionOutput + { + Message = new CompletionMessage + { + Content = "Done" + } + } + } + })); + + var grok = new GrokChatClient(client.Object, "grok-4-1-fast"); + var messages = new List + { + new(ChatRole.User, "What's the time?"), + new(ChatRole.Assistant, [new FunctionCallContent("call-123", "get_time")]), + new(ChatRole.Tool, [new FunctionResultContent("call-123", "2024-01-01T00:00:00Z")]), + }; + + await grok.GetResponseAsync(messages); + + Assert.NotNull(capturedRequest); + var toolMessage = capturedRequest.Messages.FirstOrDefault(m => m.Role == MessageRole.RoleTool); + Assert.NotNull(toolMessage); + Assert.Equal("call-123", toolMessage.ToolCallId); + } + + [Fact] + public async Task GrokSetsToolCallIdOnlyWhenCallIdIsProvided() + { + GetCompletionsRequest? capturedRequest = null; + var client = new Mock(MockBehavior.Strict); + client.Setup(x => x.GetCompletionAsync(It.IsAny(), null, null, CancellationToken.None)) + .Callback((req, _, _, _) => capturedRequest = req) + .Returns(CallHelpers.CreateAsyncUnaryCall(new GetChatCompletionResponse + { + Outputs = + { + new CompletionOutput + { + Message = new CompletionMessage + { + Content = "Done" + } + } + } + })); + + var grok = new GrokChatClient(client.Object, "grok-4-1-fast"); + var messages = new List + { + new(ChatRole.User, "What's the time?"), + new(ChatRole.Assistant, [new FunctionCallContent("call-456", "get_time")]), + // FunctionResultContent with empty CallId + new(ChatRole.Tool, [new FunctionResultContent("", "2024-01-01T00:00:00Z")]), + }; + + await grok.GetResponseAsync(messages); + + Assert.NotNull(capturedRequest); + var toolMessage = capturedRequest.Messages.FirstOrDefault(m => m.Role == MessageRole.RoleTool); + Assert.NotNull(toolMessage); + // ToolCallId should not be set if CallId is empty + Assert.False(toolMessage.HasToolCallId); + } + record Response(DateOnly Today, string Release, decimal Price); } diff --git a/src/xAI/GrokChatClient.cs b/src/xAI/GrokChatClient.cs index 9f44133..84e5988 100644 --- a/src/xAI/GrokChatClient.cs +++ b/src/xAI/GrokChatClient.cs @@ -176,11 +176,14 @@ GetCompletionsRequest MapToRequest(IEnumerable messages, ChatOption } else if (content is FunctionResultContent resultContent) { - request.Messages.Add(new Message + var msg = new Message { Role = MessageRole.RoleTool, Content = { new Content { Text = JsonSerializer.Serialize(resultContent.Result) ?? "null" } } - }); + }; + if (resultContent.CallId is { Length: > 0 } callId) + msg.ToolCallId = callId; + request.Messages.Add(msg); } else if (content is McpServerToolResultContent mcpResult && mcpResult.RawRepresentation is ToolCall mcpToolCall &&