OpenAI
Build an OpenAI agent with Scalekit-authenticated tools. Convert Scalekit's tool schemas to OpenAI's function calling format in one step.
Build an agent using OpenAI’s GPT models that reads a user’s Gmail inbox. Scalekit’s tool schemas use input_schema: rename it to parameters and wrap it in OpenAI’s function format.
Install
Section titled “Install”pip install scalekit-sdk-python openainpm install @scalekit-sdk/node openaiInitialize
Section titled “Initialize”import os, jsonimport scalekit.clientfrom openai import OpenAIfrom google.protobuf.json_format import MessageToDict
scalekit_client = scalekit.client.ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"),)actions = scalekit_client.actionsclient = OpenAI()import { ScalekitClient } from '@scalekit-sdk/node';import OpenAI from 'openai';
const scalekit = new ScalekitClient( process.env.SCALEKIT_ENVIRONMENT_URL!, process.env.SCALEKIT_CLIENT_ID!, process.env.SCALEKIT_CLIENT_SECRET!,);const openai = new OpenAI();Connect the user to Gmail
Section titled “Connect the user to Gmail”# Connect the user's Gmail account, and wait until it's ACTIVE before calling toolsconnection_name = "gmail"identifier = "user_123" # your app's unique user ID
response = actions.get_or_create_connected_account( connection_name=connection_name, identifier=identifier)if response.connected_account.status != "ACTIVE": link = actions.get_authorization_link( connection_name=connection_name, identifier=identifier ) print("Authorize Gmail:", link.link) input("Press Enter after authorizing...") # Fetch the account again to pick up the new status response = actions.get_or_create_connected_account( connection_name=connection_name, identifier=identifier )
if response.connected_account.status != "ACTIVE": raise RuntimeError( f"Gmail is {response.connected_account.status}, not ACTIVE. Authorize it and run again." )import { createInterface } from 'node:readline/promises';
// Connect the user's Gmail account, and wait until it's ACTIVE before calling toolsconst connectionName = 'gmail';const identifier = 'user_123'; // your app's unique user IDconst ACTIVE = 1; // ConnectorStatus.ACTIVE
let { connectedAccount } = await scalekit.actions.getOrCreateConnectedAccount({ connectionName, identifier,});if (connectedAccount?.status !== ACTIVE) { const { link } = await scalekit.actions.getAuthorizationLink({ connectionName, identifier }); console.log('Authorize Gmail:', link); const rl = createInterface({ input: process.stdin, output: process.stdout }); await rl.question('Press Enter after authorizing...'); rl.close(); // Fetch the account again to pick up the new status ({ connectedAccount } = await scalekit.actions.getOrCreateConnectedAccount({ connectionName, identifier, }));}
if (connectedAccount?.status !== ACTIVE) { throw new Error(`Gmail is not ACTIVE (status ${connectedAccount?.status}). Authorize it and run again.`);}See Authorize a user for production auth handling.
Run the agent
Section titled “Run the agent”Fetch tools scoped to this user, convert to OpenAI’s function format, then run the tool-calling loop:
# Fetch and convert tools to OpenAI formatscoped_response, _ = actions.tools.list_scoped_tools( identifier="user_123", filter={"connection_names": ["gmail"]}, page_size=100, # fetch beyond the default page so no connector tools are missed)llm_tools = [ { "type": "function", "function": { "name": MessageToDict(t.tool).get("definition", {}).get("name"), "description": MessageToDict(t.tool).get("definition", {}).get("description", ""), "parameters": MessageToDict(t.tool).get("definition", {}).get("input_schema", {}), }, } for t in scoped_response.tools]
# Run the agent loopmessages = [{"role": "user", "content": "Fetch my last 5 unread emails and summarize them"}]
while True: response = client.chat.completions.create( model="gpt-4o", tools=llm_tools, messages=messages, ) message = response.choices[0].message if not message.tool_calls: print(message.content) break
messages.append(message) for tc in message.tool_calls: result = actions.execute_tool( tool_name=tc.function.name, identifier="user_123", connection_name="gmail", tool_input=json.loads(tc.function.arguments), ) messages.append({ "role": "tool", "tool_call_id": tc.id, "content": str(result.data), })// Fetch and convert tools to OpenAI formatconst { tools } = await scalekit.tools.listScopedTools('user_123', { filter: { connectionNames: ['gmail'] }, pageSize: 100, // fetch beyond the default page so no connector tools are missed});// Tool definitions are typed as generic JSON, so read each field explicitlyconst definitions = tools.flatMap((t) => (t.tool?.definition ? [t.tool.definition] : []));const llmTools: OpenAI.ChatCompletionTool[] = definitions.map((definition) => ({ type: 'function', function: { name: String(definition.name), description: String(definition.description ?? ''), parameters: definition.input_schema as OpenAI.FunctionParameters, },}));
// Run the agent loopconst messages: OpenAI.ChatCompletionMessageParam[] = [ { role: 'user', content: 'Fetch my last 5 unread emails and summarize them' },];
while (true) { const response = await openai.chat.completions.create({ model: 'gpt-4o', tools: llmTools, messages, }); const message = response.choices[0].message; if (!message.tool_calls?.length) { console.log(message.content); break; } messages.push(message); for (const tc of message.tool_calls) { if (tc.type !== 'function') continue; // only function tools are registered const result = await scalekit.actions.executeTool({ toolName: tc.function.name, identifier: 'user_123', connector: 'gmail', toolInput: JSON.parse(tc.function.arguments), }); messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(result.data) }); }}Use the Responses API
Section titled “Use the Responses API”OpenAI’s Responses API is a stateful alternative to Chat Completions. Instead of managing conversation history yourself, you pass previous_response_id to continue a session. It takes tools in a flat shape, {type, name, description, parameters}, without the function wrapper that Chat Completions uses, so convert the tools first.
# Responses API tools are flat: no "function" wrapper.# strict=False because Scalekit schemas don't follow strict mode's rules.response_tools = [{"type": "function", **t["function"], "strict": False} for t in llm_tools]
response = client.responses.create( model="gpt-4o", input="Fetch my last 5 unread emails and summarize them", tools=response_tools,)
while any(item.type == "function_call" for item in response.output): tool_results = [ { "type": "function_call_output", "call_id": item.call_id, "output": str(actions.execute_tool( tool_name=item.name, identifier="user_123", connection_name="gmail", tool_input=json.loads(item.arguments), ).data), } for item in response.output if item.type == "function_call" ] response = client.responses.create( model="gpt-4o", previous_response_id=response.id, input=tool_results, tools=response_tools, )
for item in response.output: if item.type == "message": print(item.content[0].text)// Responses API tools are flat: no `function` wrapper.// strict: false because Scalekit schemas don't follow strict mode's rules.const responseTools: OpenAI.Responses.FunctionTool[] = definitions.map((definition) => ({ type: 'function', name: String(definition.name), description: String(definition.description ?? ''), parameters: definition.input_schema as Record<string, unknown>, strict: false,}));
let response = await openai.responses.create({ model: 'gpt-4o', input: 'Fetch my last 5 unread emails and summarize them', tools: responseTools,});
while (response.output.some(item => item.type === 'function_call')) { const toolResults = await Promise.all( response.output .filter(item => item.type === 'function_call') .map(async item => { const result = await scalekit.actions.executeTool({ toolName: item.name, identifier: 'user_123', connector: 'gmail', toolInput: JSON.parse(item.arguments), }); return { type: 'function_call_output' as const, call_id: item.call_id, output: JSON.stringify(result.data), }; }) ); response = await openai.responses.create({ model: 'gpt-4o', previous_response_id: response.id, input: toolResults, tools: responseTools, });}
const message = response.output.find(item => item.type === 'message');if (message?.type === 'message') { for (const part of message.content) { if (part.type === 'output_text') console.log(part.text); }}Use MCP instead
Section titled “Use MCP instead”If you prefer the MCP approach, connect your OpenAI agent via the Vercel AI SDK + MCP or LangChain’s MCP client with a Scalekit-generated URL. See Virtual MCP Servers for the URL setup.