Claude client functions use the tool_use → tool_result loop with and without a server prompt. Your client executes the function. Formats and restrictions depend on the selected API.
{
"model": "claude-opus-5",
"max_tokens": 1024,
"tools": [{
"name": "get_weather",
"description": "Weather by city",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}],
"messages": [{"role": "user", "content": "What's the weather in Moscow?"}]
}The response includes a tool_use block with the tool name and arguments — run it on your side and return the result as a tool_result block.
{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": "toolu_…",
"content": "Moscow: 18 °C, clear"
}]
}Codex supports the client tool loop on Responses, Chat Completions, and Messages; the client executes functions. GPT-6 Sol and GPT-6 Luna advertise parallel tool calls. Preserve original tool/call IDs when returning results and do not translate them across formats yourself.
| Family | Client tools |
|---|---|
| Kimi | Available in Messages, Chat Completions and Responses; tool and selection formats depend on the API |
| Composer | Available on all three surfaces; the client executes the tool. Only automatic tool_choice is supported |
| Grok | Available on all three surfaces; the client executes the tool. Only automatic tool_choice is supported |
toolsmust be an array of objects, a function must have a name, names must be unique.- The argument schema is checked for up to 32 levels of depth, 4096 nodes and 256 KiB in canonical form. Going over returns a local
400before any charge. - On
kimi-for-codingandkimi-for-coding-highspeedthe valuetool_choice: "required"is not supported and is rejected locally. Onk3andk3-256kit passes.
The loop works on all three surfaces in their own format. The model returns a call; the client executes the tool and returns its result in the same conversation. Until the result arrives, the request remains unfinished.
| Surface | What arrives instead of an answer | What you answer with |
|---|---|---|
/v1/messages | tool_use blocks in content and stop_reason: "tool_use" | a tool_result block with the same tool_use_id |
/v1/chat/completions | choices[0].message.tool_calls and finish_reason: "tool_calls" | a message with role tool and a tool_call_id |
/v1/responses | a type: "function_call" item in output with a call_id | a function_call_output item with the same call_id |
An unfinished tool loop has a result-wait limit and a total duration limit. Complete calls in the same conversation and account for the limits below.
| Limit | Value | What happens past it |
|---|---|---|
| Answering a tool call | 120 seconds from the moment you received the turn with the call | the run is closed and the part of it already spent is charged. Coming back later starts the conversation from scratch, and you pay for the input a second time |
| Length of one run | 600 seconds | 504 cursor_run_timeout, or an error event with the same code in a stream. The run is charged |
| Unfinished conversations per key | Concurrent loops waiting for tool results are limited per key | 429 cursor_key_pause_limit before money is reserved, so the refusal is free. Finish or drop one conversation to start the next |
The model only selects a function and produces arguments. Your application executes it and returns the result through the same API. Guard does not execute client functions.
- Send
toolsand the user request. - Receive the tool/function call and preserve its ID plus the complete assistant item/message.
- Execute the function locally.
- Return the result with the original ID and request the final answer.
| API | Call | Result |
|---|---|---|
| Chat | message.tool_calls[].id | assistant message + role: tool, tool_call_id |
| Messages | tool_use.id | assistant content + user tool_result.tool_use_id |
| Responses | function_call.call_id | previous_response_id + function_call_output.call_id |
const apiKey = process.env.GUARD_API_KEY;
if (!apiKey) throw new Error("GUARD_API_KEY is not set");
const url = "https://api.guardrelay.ai/glm/v1/chat/completions";
async function request(body) {
const response = await fetch(url, {
method: "POST",
headers: {
authorization: "Bearer " + apiKey,
"content-type": "application/json",
},
body: JSON.stringify(body),
});
if (!response.ok) throw new Error(await response.text());
return response.json();
}
const tools = [{
type: "function",
function: {
name: "lookup_invoice",
description: "Return one invoice amount",
parameters: {
type: "object",
properties: { code: { type: "string" } },
required: ["code"],
},
},
}];
const user = { role: "user", content: "Look up invoice QA-42" };
const first = await request({
model: "glm-5.3",
messages: [user],
tools,
tool_choice: { type: "function", function: { name: "lookup_invoice" } },
max_tokens: 4096,
});
const assistant = first.choices[0].message;
const call = assistant.tool_calls[0];
const result = { amount: 7319 }; // execute your real function here
const second = await request({
model: "glm-5.3",
messages: [
user,
assistant,
{ role: "tool", tool_call_id: call.id, content: JSON.stringify(result) },
],
tools,
max_tokens: 4096,
});
console.log(second.choices[0].message.content);Do not rebuild the assistant message from only tool_use: preserve all content, including thinking blocks and the original tool_use.id.
const toolUse = first.content.find(block => block.type === "tool_use");
if (!toolUse) throw new Error("GLM did not request the tool");
const nextMessages = [
{ role: "user", content: "Look up invoice QA-42" },
{ role: "assistant", content: first.content },
{
role: "user",
content: [{
type: "tool_result",
tool_use_id: toolUse.id,
content: JSON.stringify({ amount: 7319 }),
}],
},
];{
"model": "glm-5.3",
"previous_response_id": "<first response.id>",
"input": [{
"type": "function_call_output",
"call_id": "<function_call.call_id>",
"output": "{\"amount\":7319}"
}],
"max_output_tokens": 4096
}Client tools work in all three APIs: the model returns a call, your client runs the function and sends the result. tool_choice takes auto, none, a required call (required or any), and a call of a specific function; none turns off both functions and search. A required call and a call of a specific function cannot be combined with search in one request: such a request gets the error gemini_tool_choice_unsupported. Function names do not repeat within one request, and strict: true is not accepted. Gemini accepts no tools other than functions and search (gemini_tool_unsupported). Send an image in a tool result as base64: a link there gets gemini_inline_media_required.
Client tools work in all three APIs and go to Meta unchanged: the model returns a call, your client runs the function and sends the result. Provider-side MCP tools are not available through Guard: a request with them gets the error unsupported_tool.