---
title: OpenAI
description: Use @codespar/openai to give GPT agents commerce capabilities in Latin America.
---

import { Callout } from "fumadocs-ui/components/callout";
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
import { Steps, Step } from "fumadocs-ui/components/steps";

# OpenAI Adapter

<VersionBadge pkg="@codespar/openai" />

The `@codespar/openai` adapter converts CodeSpar session tools into OpenAI's function-calling format and provides helpers to handle tool calls in the response loop. It works with the official `openai` Node.js SDK and supports both `gpt-4o` and `gpt-4-turbo` models.

<Callout type="info">
**Pick this adapter when** your stack is already on OpenAI, you want the largest ecosystem of existing GPT prompts and tool-calling examples, or you need cost flexibility across GPT-4o / GPT-4-turbo / GPT-4o-mini for different commerce tiers.
</Callout>

## Framework-specific notes

- **`tool_call_id` must round-trip** — for each `tool_call` in the assistant message, your follow-up user message has to include a `role: "tool"` entry with the matching `tool_call_id` and the JSON-stringified result. `handleToolCall` returns a string ready to pass as `content`.
- **Parallel tool calls are opt-out by default** — GPT-4o issues multiple `tool_calls` in one response. The reference loop below iterates all of them before calling the model again; skip that loop and you will drop calls.
- **Cost-tier routing** — different commerce tiers can use different models. A cheap `gpt-4o-mini` for `codespar_discover` + `codespar_notify`, upgrade to `gpt-4o` for `codespar_pay` + `codespar_invoice` decisions where correctness matters.
- **JSON mode is not the move** — use function calling, not `response_format: "json_object"`, for tool selection. Structured outputs (new Schemas API) is fine for the final agent response shape, but tools should stay in the function-call channel.

## Installation

<Tabs items={["npm", "pnpm", "yarn"]}>
<Tab value="npm">
```bash
npm install @codespar/sdk @codespar/openai openai
```
</Tab>
<Tab value="pnpm">
```bash
pnpm add @codespar/sdk @codespar/openai openai
```
</Tab>
<Tab value="yarn">
```bash
yarn add @codespar/sdk @codespar/openai openai
```
</Tab>
</Tabs>

<Callout type="info">
`@codespar/openai` has peer dependencies on `@codespar/sdk@^0.10.0` and `openai@^4.0.0`. Make sure both are installed.
</Callout>
## API Reference

### `getTools(session): Promise<OpenAI.ChatCompletionTool[]>`

Fetches all tools from the session and converts them to OpenAI's `ChatCompletionTool[]` format. Each tool is wrapped as a `function` type with `name`, `description`, and `parameters` (JSON Schema).

```typescript
import { CodeSpar } from "@codespar/sdk";
import { getTools } from "@codespar/openai";

const codespar = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY });
const session = await codespar.create("user_123", {
  servers: ["stripe", "mercadopago"],
});

const tools = await getTools(session);
console.log(JSON.stringify(tools[0], null, 2));
```

```json title="Output: OpenAI.ChatCompletionTool"
{
  "type": "function",
  "function": {
    "name": "codespar_charge",
    "description": "Create an inbound charge (buyer pays merchant) — Pix / boleto / card",
    "parameters": {
      "type": "object",
      "properties": {
        "provider": {
          "type": "string",
          "description": "Payment provider to use (e.g., stripe, mercadopago)"
        },
        "amount": {
          "type": "number",
          "description": "Amount in cents (e.g., 4990 for R$49.90)"
        },
        "currency": {
          "type": "string",
          "description": "ISO 4217 currency code (e.g., BRL)"
        },
        "description": {
          "type": "string",
          "description": "Product or service description"
        },
        "payment_methods": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Accepted payment methods (pix, card, boleto)"
        }
      },
      "required": ["provider", "amount", "currency"]
    }
  }
}
```

<Callout type="warn">
`getTools` is async because it calls `session.tools()` under the hood. Always `await` it. Forgetting to await will pass a Promise instead of an array to `openai.chat.completions.create`, causing a runtime error.
</Callout>
### `toOpenAITool(tool): OpenAI.ChatCompletionTool`

Converts a single CodeSpar tool definition to OpenAI's `ChatCompletionTool` format. Use this when you have already fetched tools via `session.tools()` and want to convert them individually -- for example, to filter or transform tools before passing them to GPT.

```typescript
import { toOpenAITool } from "@codespar/openai";

const allTools = await session.tools();

// Filter to only shipping tools
const shippingTools = allTools
  .filter((t) => t.name === "codespar_ship")
  .map(toOpenAITool);

const response = await openai.chat.completions.create({
  model: "gpt-4o",
  tools: shippingTools,
  messages,
});
```

The function maps CodeSpar's `input_schema` to OpenAI's `parameters` field:

```typescript
// Input: CodeSpar Tool
{ name: string; description: string; input_schema: JSONSchema }

// Output: OpenAI.ChatCompletionTool
{ type: "function", function: { name: string; description: string; parameters: JSONSchema } }
```

### `handleToolCall(session, toolCall): Promise<string>`

Executes an OpenAI tool call against the CodeSpar session. It extracts the function name and arguments from the `ChatCompletionMessageToolCall`, calls `session.execute()`, and returns the result as a JSON string (ready to be used as a `tool` message content).

```typescript
import { handleToolCall } from "@codespar/openai";

// toolCall comes from response.choices[0].message.tool_calls
// {
//   id: "call_abc123",
//   type: "function",
//   function: { name: "codespar_charge", arguments: "{...}" }
// }

const result = await handleToolCall(session, toolCall);
console.log(result);
```

```json title="Return value (string)"
"{\"charge_id\":\"pay_7f8g9h0i1j2k\",\"qr_code\":\"00020126360014BR.GOV.BCB.PIX0114...\",\"amount\":4990,\"currency\":\"BRL\",\"status\":\"pending\"}"
```

<Callout type="info">
Unlike the Claude adapter's `handleToolUse` which returns `unknown`, `handleToolCall` returns a `string`. This matches OpenAI's expectation that tool message `content` is always a string.
</Callout>
## Full agent loop

This is a complete, production-ready example of a GPT agent that processes commerce operations in Latin America:

```typescript title="openai-agent.ts"
import OpenAI from "openai";
import { CodeSpar } from "@codespar/sdk";
import { getTools, handleToolCall } from "@codespar/openai";

const openai = new OpenAI();
const codespar = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY });

async function run(userMessage: string) {
  // 1. Create a session with the servers you need
  const session = await codespar.create("user_123", {
    servers: ["stripe", "asaas", "correios"],
  });

  // 2. Get tools in OpenAI format
  const tools = await getTools(session);

  // 3. Build the initial messages
  const messages: OpenAI.ChatCompletionMessageParam[] = [
    {
      role: "system",
      content:
        "You are a commerce assistant for a Brazilian e-commerce store. " +
        "Use the available tools to handle payments, invoicing, and shipping. " +
        "Always confirm amounts and details before processing payments. " +
        "Respond in the same language the user writes in.",
    },
    { role: "user", content: userMessage },
  ];

  // 4. First completion
  let response = await openai.chat.completions.create({
    model: "gpt-4o",
    tools,
    messages,
  });

  let message = response.choices[0].message;

  // 5. Tool call loop
  const MAX_ITERATIONS = 10;
  let iterations = 0;

  while (
    message.tool_calls &&
    message.tool_calls.length > 0 &&
    iterations < MAX_ITERATIONS
  ) {
    // Add assistant message with tool calls
    messages.push(message);

    // Execute each tool call and add results
    for (const toolCall of message.tool_calls) {
      let content: string;
      try {
        content = await handleToolCall(session, toolCall);
      } catch (error) {
        content = JSON.stringify({
          error: error instanceof Error ? error.message : "Tool call failed",
        });
      }

      messages.push({
        role: "tool",
        tool_call_id: toolCall.id,
        content,
      });
    }

    // Next completion
    response = await openai.chat.completions.create({
      model: "gpt-4o",
      tools,
      messages,
    });

    message = response.choices[0].message;
    iterations++;
  }

  // 6. Clean up
  await session.close();

  return message.content ?? "";
}

// Usage
const reply = await run("Generate a boleto for R$250 due in 7 days");
console.log(reply);
```

## Handling parallel tool calls

GPT-4o may return multiple tool calls in a single response. The OpenAI protocol requires you to return results for **all** tool calls before making the next completion request:

```typescript
// GPT returns multiple tool calls
// message.tool_calls = [
//   { id: "call_1", function: { name: "codespar_charge", arguments: "..." } },
//   { id: "call_2", function: { name: "codespar_notify", arguments: "..." } }
// ]

messages.push(message);

// Execute all in parallel for better performance
const results = await Promise.all(
  message.tool_calls.map(async (toolCall) => {
    const content = await handleToolCall(session, toolCall);
    return { toolCall, content };
  })
);

// Add all results to messages
for (const { toolCall, content } of results) {
  messages.push({
    role: "tool",
    tool_call_id: toolCall.id,
    content,
  });
}
```

<Callout type="warn">
You must return a tool result for every tool call in the response. Omitting a result will cause OpenAI to return a 400 error on the next completion request.
</Callout>
## Streaming

The adapter works with streaming responses. Use `openai.chat.completions.create` with `stream: true`, accumulate the streamed chunks, then handle tool calls after the stream completes:

```typescript title="openai-streaming.ts"
import OpenAI from "openai";
import { CodeSpar } from "@codespar/sdk";
import { getTools, handleToolCall } from "@codespar/openai";

const openai = new OpenAI();
const codespar = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY });

async function runStreaming(userMessage: string) {
  const session = await codespar.create("user_123", {
    servers: ["stripe", "mercadopago"],
  });

  const tools = await getTools(session);

  const messages: OpenAI.ChatCompletionMessageParam[] = [
    {
      role: "system",
      content: "You are a commerce assistant for a Brazilian store.",
    },
    { role: "user", content: userMessage },
  ];

  let continueLoop = true;

  while (continueLoop) {
    const stream = await openai.chat.completions.create({
      model: "gpt-4o",
      tools,
      messages,
      stream: true,
    });

    // Accumulate the streamed response
    let assistantContent = "";
    const toolCalls: OpenAI.ChatCompletionMessageToolCall[] = [];
    const toolCallArgs: Record<number, string> = {};

    for await (const chunk of stream) {
      const delta = chunk.choices[0]?.delta;

      if (delta?.content) {
        process.stdout.write(delta.content);
        assistantContent += delta.content;
      }

      if (delta?.tool_calls) {
        for (const tc of delta.tool_calls) {
          if (tc.id) {
            toolCalls[tc.index] = {
              id: tc.id,
              type: "function",
              function: { name: tc.function?.name ?? "", arguments: "" },
            };
          }
          if (tc.function?.arguments) {
            toolCallArgs[tc.index] =
              (toolCallArgs[tc.index] ?? "") + tc.function.arguments;
          }
        }
      }
    }

    // Finalize tool call arguments
    for (const [index, args] of Object.entries(toolCallArgs)) {
      if (toolCalls[Number(index)]) {
        toolCalls[Number(index)].function.arguments = args;
      }
    }

    const validToolCalls = toolCalls.filter(Boolean);

    if (validToolCalls.length > 0) {
      messages.push({
        role: "assistant",
        content: assistantContent || null,
        tool_calls: validToolCalls,
      });

      for (const toolCall of validToolCalls) {
        let content: string;
        try {
          content = await handleToolCall(session, toolCall);
        } catch (error) {
          content = JSON.stringify({
            error: error instanceof Error ? error.message : "Tool call failed",
          });
        }
        messages.push({
          role: "tool",
          tool_call_id: toolCall.id,
          content,
        });
      }
    } else {
      continueLoop = false;
    }
  }

  await session.close();
}

await runStreaming("Create a Pix payment for R$150");
```

## Error handling

### Tool execution errors

Wrap `handleToolCall` in a try-catch and return errors as tool message content. This lets GPT reason about the failure and decide what to do next:

```typescript
for (const toolCall of message.tool_calls) {
  let content: string;
  try {
    content = await handleToolCall(session, toolCall);
  } catch (error) {
    content = JSON.stringify({
      error: error instanceof Error ? error.message : "Tool call failed",
      tool_name: toolCall.function.name,
    });
  }

  messages.push({
    role: "tool",
    tool_call_id: toolCall.id,
    content,
  });
}
```

<Callout type="info">
Returning errors as tool results (instead of throwing) lets GPT reason about the failure. It may retry with different parameters, ask the user for clarification, or suggest an alternative approach.
</Callout>
### API errors

Handle OpenAI-specific errors like rate limits and context length:

```typescript
try {
  response = await openai.chat.completions.create({
    model: "gpt-4o",
    tools,
    messages,
  });
} catch (error) {
  if (error instanceof OpenAI.RateLimitError) {
    // Implement exponential backoff
    await new Promise((resolve) => setTimeout(resolve, 1000));
    // Retry...
  } else if (error instanceof OpenAI.BadRequestError) {
    // Context length exceeded -- truncate messages
    console.error("Context too long:", error.message);
  }
}
```

## Best practices

1. **Always close sessions.** Use `try/finally` to ensure `session.close()` runs even if the loop throws an exception.

2. **Scope servers narrowly.** Only connect the MCP servers your agent actually needs. Fewer servers means fewer tools, which improves GPT's tool selection accuracy.

3. **Use `gpt-4o` for tool calling.** It has the best function-calling accuracy. `gpt-4-turbo` works but may be less reliable with complex tool schemas.

4. **Set a descriptive system prompt.** Tell GPT what domain it operates in and what tools to prefer. This reduces unnecessary `codespar_discover` calls.

5. **Return errors as tool results.** Never let `handleToolCall` exceptions crash the loop. Return them as structured JSON so GPT can self-correct.

6. **Limit loop iterations.** Add a maximum iteration count (10 is a good default) to prevent infinite tool-call loops.

## Newer SDK wrappers

The adapter pattern above wires GPT to `session.tools()` + `session.execute()`. For higher-level flows you can call typed wrappers on the session directly — they hit the same routing infrastructure but skip the LLM hop:

- `session.discover(query)` / `session.charge(args)` / `session.pay(args)` / `session.ship(args)` — typed shortcuts for the meta-tools.
- `session.connectionWizard(serverId)` — open a hosted auth flow for a missing connection.
- `session.paymentStatus(toolCallId)` and `session.paymentStatusStream(toolCallId)` — async settlement correlation (poll or SSE).
- `session.verificationStatus(toolCallId)` and `session.verificationStatusStream(toolCallId)` — KYC outcome polling / SSE.

Full reference at [/docs/api/sdk](/docs/api/sdk).

## Next steps

<NextStepsGrid items={[
  { label: "CONCEPT", title: "Sessions", description: "Session lifecycle and configuration.", href: "/docs/concepts/sessions" },
  { label: "CONCEPT", title: "Tools & Meta-Tools", description: "Meta-tools and how routing works.", href: "/docs/concepts/tools" },
  { label: "PROVIDER", title: "Claude Adapter", description: "If you prefer Anthropic models for tool-calling accuracy.", href: "/docs/providers/claude" },
  { label: "PROVIDER", title: "Vercel AI SDK", description: "Framework-agnostic with automatic tool execution.", href: "/docs/providers/vercel" },
  { label: "QUICKSTART", title: "Quickstart", description: "End-to-end setup in under 5 minutes.", href: "/docs/quickstart" },
]} />
