# OpenAI-Compatible API

GoClaw's gateway serves an OpenAI-compatible `POST /v1/chat/completions`, streaming and non-streaming, so OpenAI SDK clients and tools such as Cursor can talk to the default agent without a GoClaw-specific client.

`internal/gateway/openai_compat.go`

## Architecture

```mermaid
flowchart TD
    REQ["<b>POST /v1/chat/completions</b>"] --> PARSE["<b>Parse OAIRequest</b><br/>• model, messages, stream (max_tokens, temperature ignored)<br/>• Last user message = prompt; earlier messages = history<br/>• Session: openai-compat:&lt;client-ip&gt; (no history written)<br/>• Scope: default agent + channel=openai"]

    PARSE --> SUB{"/subagents command?"}
    SUB -->|Yes| SUBH["<b>HandleSubagentCommand</b><br/>Pass-through"]
    SUB -->|No| STR{"stream?"}

    STR -->|"stream = false"| SYNC["<b>ProcessStream → DrainToResultObserved</b><br/>→ OAIResponse JSON"]
    STR -->|"stream = true"| SSE["<b>ProcessStream events → SSE stream</b><br/>data: {&quot;choices&quot;:[{&quot;delta&quot;:{&quot;content&quot;:…}}]}<br/>data: [DONE]"]
```

## Feature Comparison

| # | Feature | OpenClaw | GoClaw | Status |
|---|---------|----------|---------|--------|
| 1.1 | `/v1/chat/completions` POST | ✅ | ✅ `HandleChatCompletions` | Done |
| 1.2 | Streaming (SSE) | ✅ | ✅ `handleStreamingCompletion` — `text/event-stream` | Done |
| 1.3 | Non-streaming | ✅ | ✅ JSON response | Done |
| 1.4 | Token usage estimation | ✅ | ✅ ~4 chars/token approximation | Done |
| 1.5 | **Subagent command routing** | — | ✅ `HandleSubagentCommand` pass-through (`/subagents spawn\|list\|status\|stop`) | **GoClaw only** |
| 1.6 | Session persistence | ✅ | A session `openai-compat:<ip>` is created, but no history is written to it: the client sends the conversation in `messages` | Partial |
| 1.7 | **Error formatting** | ✅ | ✅ `UserFacingErrorMessage` → OpenAI error JSON | Done |
| 1.8 | **Trust proxy IP** | — | ✅ `extractIPFromRequest` respects `trustProxy` config | **GoClaw only** |
| 1.9 | Model routing | model → provider | Uses default agent | Partial |
| 1.10 | `/v1/models` list | ✅ | — | **TODO** |
| 1.11 | Function calling pass-through | ✅ | — | **TODO** |
| 1.12 | Embeddings API | ✅ | — | **TODO** |
| 1.13 | Multi-model support | ✅ model param → provider | — | **TODO** |

### OpenAI-Compatible Types

| Type | Fields |
|------|--------|
| `OAIRequest` | model, messages[], stream, max_tokens, temperature (`max_tokens` and `temperature` are read and ignored; each message's `content` must be a string) |
| `OAIResponse` | id, object, created, model, choices[], usage |
| `OAIStreamChunk` | id, object, created, model, choices[] (index, delta, finish_reason) |
| `OAIUsage` | prompt_tokens, completion_tokens, total_tokens |

### Streaming Format
```
data: {"id":"chatcmpl-xxx","object":"chat.completion.chunk","created":1760000000,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"assistant","content":"chunk"},"finish_reason":null}]}
data: {"id":"chatcmpl-xxx","object":"chat.completion.chunk","created":1760000000,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"","content":""},"finish_reason":"stop"}]}
data: [DONE]
```

Two more frames can appear in a stream. `data: {"buffer_reset":true}` tells the
client to discard the text received so far (the reply is being retried).
`data: {"error":{"message":"…"}}` reports a failure and ends the stream: no finish
chunk and no `data: [DONE]` follow it.
