Skip to content

Claude Agent SDK: using the VIDAI Control Plane as the backend

The Claude Agent SDK constructs an Anthropic client under the hood. Pass base_url= when you initialise the SDK's client (or set the equivalent env vars) and every agent turn, tool call, and long-running task routes through the control plane.

TL;DR

export ANTHROPIC_BASE_URL=https://your-vidai-server.example.com
export ANTHROPIC_API_KEY=your-vidai-key
from claude_agent_sdk import query

async for message in query("reply with: ok"):
    print(message)

Prerequisites

  • pip install claude-agent-sdk.
  • Control plane base URL and an API key from API Keys.
  • A Claude-family model registered on the Models page.

Note: the Anthropic client wants the root base URL (no /v1); it appends /v1/messages itself. That's what the control plane serves on the Anthropic wire path.

Passing config explicitly

from anthropic import AsyncAnthropic
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

client = AsyncAnthropic(
    base_url="https://your-vidai-server.example.com",
    api_key="your-vidai-key",
)

async with ClaudeSDKClient(
    options=ClaudeAgentOptions(client=client),
) as agent:
    await agent.query("reply with: ok")
    async for msg in agent.receive_response():
        print(msg)

Tools

Custom tools defined with @tool are called through the same client. Every tool round-trip is a control-plane request that lands in Request Logs:

from claude_agent_sdk import tool

@tool("get_weather", "Get current weather", {"city": str})
async def get_weather(args):
    return {"content": [{"type": "text", "text": f"Sunny in {args['city']}."}]}

Multi-turn conversation

The SDK's ClaudeSDKClient holds a conversation across calls; each turn is a control-plane request:

async with ClaudeSDKClient() as agent:
    await agent.query("Draft a launch checklist.")
    async for msg in agent.receive_response(): print(msg)

    await agent.query("Now critique it.")
    async for msg in agent.receive_response(): print(msg)

Streaming

The SDK's receive_response() is streaming by default. No extra config needed — the control plane forwards the stream from the Claude upstream unchanged.

Cross-provider routing

Even though the SDK is Claude-branded, a model registered on the control plane that routes to a non-Claude upstream is callable by name. The control plane translates the Anthropic-shape request into the upstream's wire format. From the SDK's point of view it's still calling /v1/messages and getting the response shape it expects.

Verify it works

import asyncio
from claude_agent_sdk import query

async def main():
    async for message in query("reply with exactly: ok"):
        print(message)

asyncio.run(main())

A row appears on Request Logs within a few seconds.

If something's off

Raise an issue at github.com/vidaiUK/vidai-quickstart/issues with your SDK setup and the query that reproduces the issue. We'll get it sorted.

Where to go next