> ## Documentation Index
> Fetch the complete documentation index at: https://cobo.com/products/agentic-wallet/manual/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenAI Agents SDK

> Use Cobo Agentic Wallet with OpenAI Agents SDK in Python or TypeScript.

Start with the language SDK quickstart first, then move to OpenAI Agents SDK once the pact-to-execution flow works without an agent loop.

## Before you start

Complete the [CLI quickstart](/products/agentic-wallet/manual/developer/cli) first so your runtime already has a paired wallet, API key, and wallet UUID.

## 5-minute outcome

1. Create Cobo-enabled OpenAI agent
2. Submit a pact and wait for owner approval
3. Execute success plus denial contract-call sequence
4. Let agent adapt using denial context
5. Track by `request_id` and verify with audit logs

## Step 1: Install

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    pip install "cobo-agentic-wallet[openai]"
    ```
  </Tab>

  <Tab title="TypeScript">
    ```bash theme={null}
    npm install @cobo/agentic-wallet @openai/agents zod
    ```
  </Tab>
</Tabs>

## Step 2: Configure environment

```bash theme={null}
export AGENT_WALLET_API_URL=https://api.agenticwallet.cobo.com
export AGENT_WALLET_API_KEY=your-api-key
export OPENAI_API_KEY=your-openai-api-key
```

## Step 3: Run agent flow

<Tabs>
  <Tab title="Python">
    ```python title="openai_quickstart.py" theme={null}
    import asyncio
    import os

    from agents import Runner

    from cobo_agentic_wallet import WalletAPIClient
    from cobo_agentic_wallet.integrations.openai import create_cobo_agent, create_cobo_agent_context

    INCLUDE_TOOLS = [
        "submit_pact", "get_pact", "transfer_tokens",
        "estimate_transfer_fee", "get_transaction_record_by_request_id", "get_audit_logs",
    ]


    async def main() -> None:
        _ = os.environ["OPENAI_API_KEY"]  # consumed implicitly by the OpenAI Agents runner
        wallet_id = os.environ["AGENT_WALLET_WALLET_ID"]
        destination = os.environ.get("CAW_DESTINATION", "0x1111111111111111111111111111111111111111")
        prompt = (
            f"Use wallet {wallet_id}. Submit a pact for a controlled transfer task and wait until "
            f"it is active. Using the newly created pact, transfer 0.001 SETH to {destination} on "
            f"SETH. Next, using the same pact, attempt 0.005 SETH. If denied, follow the denial "
            f"guidance and retry with a compliant amount. Track the result by request_id and "
            f"summarize what happened."
        )

        async with WalletAPIClient(
            base_url=os.environ["AGENT_WALLET_API_URL"],
            api_key=os.environ["AGENT_WALLET_API_KEY"],
        ) as client:
            agent = create_cobo_agent(client=client, model="gpt-4.1-mini", include_tools=INCLUDE_TOOLS)
            try:
                result = await Runner.run(
                    agent, prompt, context=create_cobo_agent_context(), max_turns=20,
                )
                print(result.final_output)
            finally:
                toolkit = getattr(agent, "_caw_toolkit", None)
                if toolkit is not None:
                    await toolkit.aclose()


    asyncio.run(main())
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript title="openai_quickstart.ts" theme={null}
    import { randomUUID } from 'node:crypto';
    import { Agent, run, tool } from '@openai/agents';
    import { z } from 'zod';

    import {
      AuditApi,
      Configuration,
      PactsApi,
      TransactionRecordsApi,
      TransactionsApi,
    } from '@cobo/agentic-wallet';

    // ─── Env ─────────────────────────────────────────────────────────────────────

    function requireEnv(name: string): string {
      const v = process.env[name];
      if (!v) throw new Error(`Missing required environment variable: ${name}`);
      return v;
    }

    const env = {
      basePath: requireEnv('AGENT_WALLET_API_URL'),
      ownerKey: requireEnv('AGENT_WALLET_API_KEY'),
      walletId: requireEnv('AGENT_WALLET_WALLET_ID'),
      openaiApiKey: requireEnv('OPENAI_API_KEY'),
      destination: process.env.CAW_DESTINATION ?? '0x1111111111111111111111111111111111111111',
    };

    // ─── Owner-scoped API clients ────────────────────────────────────────────────

    const ownerConfig = new Configuration({ apiKey: env.ownerKey, basePath: env.basePath });
    const pactsApi = new PactsApi(ownerConfig);
    const ownerTxApi = new TransactionsApi(ownerConfig);
    const recordsApi = new TransactionRecordsApi(ownerConfig);
    const auditApi = new AuditApi(ownerConfig);

    // ─── Demo pact spec ──────────────────────────────────────────────────────────
    // Allow SETH transfers up to 0.002; anything larger is denied by policy.

    const CHAIN_ID = 'SETH';
    const TOKEN_ID = 'SETH';
    const DEMO_PACT_SPEC = {
      policies: [
        {
          name: 'max-tx-limit',
          type: 'transfer',
          rules: {
            effect: 'allow',
            when: {
              chain_in: [CHAIN_ID],
              token_in: [{ chain_id: CHAIN_ID, token_id: TOKEN_ID }],
            },
            deny_if: { amount_gt: '0.002' },
          },
        },
      ],
      completion_conditions: [{ type: 'time_elapsed', threshold: '86400' }],
    };

    // ─── Pact session store ──────────────────────────────────────────────────────
    // Capture (pact_id → api_key) as submit_pact / get_pact responses come back,
    // then resolve pact-scoped TransactionsApi clients at transfer time.

    const pactApiKeys = new Map<string, string>();

    function capturePact(response: unknown): void {
      if (!response || typeof response !== 'object') return;
      const r = response as Record<string, unknown>;
      const pactId = (r.pact_id ?? r.id) as string | undefined;
      const apiKey = r.api_key as string | undefined;
      if (pactId && apiKey) pactApiKeys.set(pactId, apiKey);
    }

    function txApiForPact(pactId?: string | null): TransactionsApi {
      if (!pactId) return ownerTxApi;
      const apiKey = pactApiKeys.get(pactId);
      if (!apiKey) throw new Error(`Unknown pact_id ${pactId}. Call submit_pact or get_pact first.`);
      return new TransactionsApi(new Configuration({ apiKey, basePath: env.basePath }));
    }

    // ─── Denial handling ─────────────────────────────────────────────────────────
    // Surface policy denials back to the LLM as `{ error, suggestion }` so it can
    // self-correct, instead of aborting the agent loop with an exception.

    async function catchPolicyDenial<T>(
      work: () => Promise<T>,
    ): Promise<T | { error: unknown; suggestion?: string }> {
      try {
        return await work();
      } catch (err) {
        const data = (err as { response?: { data?: { error?: unknown; suggestion?: string } } })
          ?.response?.data;
        return { error: data?.error ?? 'UNKNOWN_ERROR', suggestion: data?.suggestion };
      }
    }

    // ─── Prompts ─────────────────────────────────────────────────────────────────

    const DEMO_USER_PROMPT =
      `Use wallet ${env.walletId}. ` +
      `Submit a pact for a controlled transfer task and wait until it is active. ` +
      `Using the newly created pact, transfer 0.001 ${TOKEN_ID} to ${env.destination} on ${CHAIN_ID}. ` +
      `Next, using the same pact, attempt 0.005 ${TOKEN_ID}. If denied, follow the denial ` +
      `guidance and retry with a compliant amount. ` +
      `Track the result by request_id and summarize what happened.`;

    // ─── Tool definitions (@openai/agents-specific) ──────────────────────────────

    const submitPact = tool({
      name: 'submit_pact',
      description: 'Submit a pact and return the pact id.',
      parameters: z.object({ wallet_id: z.string(), intent: z.string() }),
      async execute({ wallet_id, intent }) {
        const { data } = await pactsApi.submitPact({ wallet_id, intent, spec: DEMO_PACT_SPEC });
        capturePact(data.result);
        return data.result;
      },
    });

    const getPact = tool({
      name: 'get_pact',
      description: 'Fetch a pact, including its status and api_key once active.',
      parameters: z.object({ pact_id: z.string() }),
      async execute({ pact_id }) {
        const { data } = await pactsApi.getPact(pact_id);
        capturePact(data.result);
        return data.result;
      },
    });

    const estimateTransferFee = tool({
      name: 'estimate_transfer_fee',
      description: 'Estimate fees for a token transfer before submitting it.',
      parameters: z.object({
        wallet_uuid: z.string(),
        dst_addr: z.string(),
        token_id: z.string(),
        amount: z.string(),
        pact_id: z.string().nullable(),
      }),
      async execute({ wallet_uuid, dst_addr, token_id, amount, pact_id }) {
        const { data } = await txApiForPact(pact_id).estimateTransferFee(wallet_uuid, {
          chain_id: CHAIN_ID,
          dst_addr,
          token_id,
          amount,
        });
        return data.result;
      },
    });

    const transferTokens = tool({
      name: 'transfer_tokens',
      description:
        'Execute a policy-enforced transfer. A unique request_id is auto-generated ' +
        'and returned; use that value to track or look up the tx later. Pass pact_id ' +
        'to invoke under pact-scoped policy permissions.',
      parameters: z.object({
        wallet_uuid: z.string(),
        dst_addr: z.string(),
        token_id: z.string(),
        amount: z.string(),
        pact_id: z.string().nullable(),
      }),
      async execute({ wallet_uuid, dst_addr, token_id, amount, pact_id }) {
        return catchPolicyDenial(async () => {
          const { data } = await txApiForPact(pact_id).transferTokens(wallet_uuid, {
            chain_id: CHAIN_ID,
            dst_addr,
            token_id,
            amount,
            request_id: randomUUID(),
          });
          return data.result;
        });
      },
    });

    const getTransactionRecordByRequestId = tool({
      name: 'get_transaction_record_by_request_id',
      description: 'Look up a transaction record by request id.',
      parameters: z.object({ wallet_uuid: z.string(), request_id: z.string() }),
      async execute({ wallet_uuid, request_id }) {
        const { data } = await recordsApi.getUserTransactionByRequestId(wallet_uuid, request_id);
        return data.result;
      },
    });

    const getAuditLogs = tool({
      name: 'get_audit_logs',
      description: 'List recent audit log entries for the wallet.',
      parameters: z.object({
        wallet_id: z.string(),
        limit: z.number().int().positive().nullable(),
      }),
      async execute({ wallet_id, limit }) {
        const { data } = await auditApi.listAuditLogs(
          wallet_id,
          undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
          limit ?? 20,
        );
        const items = (data.result as { items?: Array<{ result?: string }> })?.items ?? [];
        return {
          items,
          allowed: items.filter(it => it.result === 'allowed').length,
          denied: items.filter(it => it.result === 'denied').length,
        };
      },
    });

    // ─── Boot ────────────────────────────────────────────────────────────────────

    const agent = new Agent({
      name: 'cobo-operator',
      model: 'gpt-4.1-mini',
      tools: [
        submitPact,
        getPact,
        transferTokens,
        estimateTransferFee,
        getTransactionRecordByRequestId,
        getAuditLogs,
      ],
    });

    const result = await run(agent, DEMO_USER_PROMPT, { maxTurns: 20 });
    console.log(result.finalOutput);
    ```

    Add `get_audit_logs` as a fourth tool when you want the model to summarize operator activity on its own. Keep it out of the first example so the core authorization and execution loop stays obvious.
  </Tab>
</Tabs>

## Step 4: Validate results

Confirm final output includes:

* pact submission and activation confirmation
* success event
* denial reason and suggestion
* successful compliant retry
* transaction lookup by `request_id`
* audit confirmation

## Keep the OpenAI layer thin

The OpenAI agent should coordinate the CAW flow, not absorb every wallet capability by default.

* expose only the tools needed for the runtime role
* keep pact submission and execution close together so the approval story stays legible
* prefer durable tracking by `request_id`
* move deterministic checks outside the agent loop when you need strict control

## Go further

In Python, `create_cobo_agent` wires up the widened CAW runtime toolkit directly. In TypeScript, the normal pattern is to wrap the CAW TypeScript SDK in OpenAI tool functions and expose only the subset your runtime needs.

* **Define custom function tools** — in Python, add functions alongside CAW's built-in adapter; in TypeScript, wrap `@cobo/agentic-wallet` API calls in framework-native tool definitions.
* **Keep CAW behind a narrow tool surface** — do not expose the whole API to the model. Start with Pact Drafting, Execution, and Observer roles.
* **Use direct SDK calls for out-of-band operations** — run pact submission, policy dry-runs, and audit queries programmatically outside the agent loop when you need deterministic behavior.
* **Combine with CLI** — use `caw` for onboarding and debugging; use the SDK tools for agent-time execution.
* **Use role-based presets** — start with Pact Drafting, Execution, and Observer responsibilities instead of exposing the entire toolkit immediately.

<CardGroup cols={2}>
  <Card title="Python SDK" href="/products/agentic-wallet/manual/developer/api-client-python">
    Use WalletAPIClient directly for custom tool functions.
  </Card>
</CardGroup>
