Handling Tool Calls
profile.tools() returns model-callable Configure functions and profile.executeTool() dispatches them. Keep your existing tool router — forward only Configure-prefixed (configure_*) calls to Configure, and run your own tools as you do today.
The token used below comes from the server-side Continue with Configure exchange or the inline Link fallback. The model never sees that token; it only sees tool schemas and tool results.
Two facts make wiring this into an existing loop a two-line change:
profile.tools()returns Anthropic-native schemas:{ name, description, input_schema }. Pass them straight to the Anthropic SDK. For OpenAI, wrap them withtoOpenAIFunctions()(exported fromconfigure).profile.executeTool()accepts either{ name, arguments }(OpenAI-style) or{ name, input }(Anthropic-style). Pass the tool call through in whichever shape your provider produced.
When the model calls configure_profile_read, that tool call is the personalization magic moment — and it shows up in your logs as a real tool call.
Anthropic
ts
import Anthropic from "@anthropic-ai/sdk";
import { Configure } from "configure";
const anthropic = new Anthropic();
const configure = new Configure({
apiKey: process.env.CONFIGURE_API_KEY,
agent: process.env.CONFIGURE_AGENT,
});
async function chat({ token, messages, system }) {
const profile = configure.profile({ token });
// profile.tools() is already Anthropic-shaped — spread it next to your own tools.
const tools = [...yourTools, ...profile.tools({ connectors: ["gmail"] })];
while (true) {
const response = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
system,
messages,
tools,
});
const toolUses = response.content.filter((block) => block.type === "tool_use");
if (!toolUses.length) {
return response.content.find((block) => block.type === "text")?.text ?? "";
}
messages.push({ role: "assistant", content: response.content });
const toolResults = [];
for (const call of toolUses) {
const result = call.name.startsWith("configure_")
? await profile.executeTool({ name: call.name, input: call.input })
: await executeYourTool(call);
toolResults.push({
type: "tool_result",
tool_use_id: call.id,
content: JSON.stringify(result),
});
}
messages.push({ role: "user", content: toolResults });
}
}OpenAI
ts
import OpenAI from "openai";
import { Configure, toOpenAIFunctions } from "configure";
const openai = new OpenAI();
const configure = new Configure({
apiKey: process.env.CONFIGURE_API_KEY,
agent: process.env.CONFIGURE_AGENT,
});
async function chat({ token, messages }) {
const profile = configure.profile({ token });
// Your own tools are already OpenAI-shaped; convert only the Configure tools.
const tools = [
...yourTools,
...toOpenAIFunctions(profile.tools({ connectors: ["gmail"] })),
];
while (true) {
const completion = await openai.chat.completions.create({
model: "gpt-4o",
messages,
tools,
tool_choice: "auto",
});
const message = completion.choices[0].message;
if (!message.tool_calls?.length) {
return message.content ?? "";
}
messages.push(message);
for (const call of message.tool_calls) {
const args = JSON.parse(call.function.arguments || "{}");
const result = call.function.name.startsWith("configure_")
? await profile.executeTool({ name: call.function.name, arguments: args })
: await executeYourTool(call);
messages.push({
role: "tool",
tool_call_id: call.id,
content: JSON.stringify(result),
});
}
}
}Verify Without A Model
To confirm the tool path is wired — in a smoke test, or when no provider key is set — call the read tool directly. This is the same dispatch the model uses, with no provider involved:
ts
const profile = configure.profile({ token });
const result = await profile.executeTool({
name: "configure_profile_read",
arguments: { sections: ["identity", "summary", "preferences", "imports"] },
});A non-error result confirms your keys, agent handle, token (or externalId), and the tool path are correct.
Framework Adapters
If your framework already drives the loop and exposes an executeTool/onToolCall callback (for example the Vercel AI SDK), you do not need the loops above. Pass a thin adapter that forwards Configure-prefixed calls:
ts
executeTool: (toolCall) =>
toolCall.name.startsWith("configure_")
? profile.executeTool(toolCall)
: executeYourTool(toolCall),This is a framework-specific shortcut, not the universal path — most providers need an explicit loop like the ones above.
Tool Set
With no options, profile.tools() returns only:
configure_profile_readconfigure_profile_searchconfigure_profile_remember
Connector tools are returned only when enabled through connectors. Action tools are returned only when enabled through actions. profile.executeTool() enforces the same enabled set: a call to configure_email_send fails unless profile.tools({ actions: ["email.send"] }) was used for that runtime handle.
profile.commit() is runtime plumbing called after the model turn. It is not part of the default model tool set.