rotom 1.5.6

OpenAI- and Anthropic-compatible local API gateway backed by OAuth providers.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
<p align="center">
  <img src="logo.png" alt="rotom logo" width="144">
</p>

# rotom

[![CI](https://github.com/RyanKung/rotom/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/RyanKung/rotom/actions/workflows/ci.yml)
[![Release](https://github.com/RyanKung/rotom/actions/workflows/release.yml/badge.svg?branch=master)](https://github.com/RyanKung/rotom/actions/workflows/release.yml)

Use Codex, Grok, Kiro, or Cursor OAuth from tools that expect OpenAI- or
Anthropic-compatible APIs.

rotom is a local Rust gateway for Claude Code, OpenAI SDKs, Anthropic SDKs, and
other API-compatible clients. Log in once with an OAuth provider, then point
your tools at the local server.

## Demo

Claude Code running through Grok:

<p align="center">
  <img src="demos/claude-grok-4.3.gif" alt="Claude Code using grok-4.3 through rotom" width="720">
</p>

Claude Code running through GPT:

<p align="center">
  <img src="demos/claude-gpt-5.5.gif" alt="Claude Code using gpt-5.5 through rotom" width="720">
</p>

## Quick Start

```bash
cargo install rotom
rotom login
rotom serve --bind 127.0.0.1:14550 --api-key local-secret
```

Use a comma-separated bind list to listen on multiple local interfaces. CIDR
entries match local IPv4 interfaces in that range:

```bash
rotom serve --bind 127.0.0.1:14550,192.168.1.0/24:14550 --api-key local-secret
```

Set Claude Code or any Anthropic-compatible client to use the local gateway:

```bash
export ANTHROPIC_BASE_URL=http://127.0.0.1:14550
export ANTHROPIC_AUTH_TOKEN=local-secret
export ANTHROPIC_MODEL="gpt-5.5"

claude
```

`login` lists the available OAuth providers and starts the selected flow. Use
`rotom login --provider openai`, `rotom login --provider grok`,
`rotom login --kiro`, or `rotom login --cursor` to skip the prompt. Codex and
Grok ask you to paste the full redirected URL from the browser address bar, for
example `http://localhost:1455/auth/callback?code=...&state=...`. This matches
OpenClaw's remote/headless fallback and does not require your gateway host to
be reachable from the public internet.

`ANTHROPIC_BASE_URL` should point at the rotom server root, not `/v1`, because
Anthropic clients append `/v1/messages` themselves.

For non-interactive validation:

```bash
ANTHROPIC_BASE_URL=http://127.0.0.1:14550 \
ANTHROPIC_AUTH_TOKEN=local-secret \
ANTHROPIC_MODEL="gpt-5.5" \
claude -p "Reply with the single word OK"
```

## Providers

Grok OAuth uses the same local credential flow with xAI's OAuth endpoints:

```bash
rotom login --provider grok
rotom serve --bind 127.0.0.1:14550 --api-key local-secret
```

Credentials are stored per provider, so logging in to Grok does not replace
Codex credentials. When both are present, `serve` exposes both Codex and Grok
models through the same local OpenAI-compatible and Anthropic-compatible routes.
Use `--provider grok` only when you intentionally want to serve one provider.
If `rotom daemon` is already running when you add a new provider, restart it so
the running service loads the new provider:

```bash
rotom daemon restart
```

xAI may still restrict OAuth API access by account tier even when browser login
succeeds.

List the model registry grouped by provider:

```bash
rotom models
rotom models --provider openai
rotom models --provider grok
rotom models --provider kiro
rotom models --provider cursor
```

Kiro login uses Kiro's own portal-style callback format and stores the result
in rotom's auth file without scanning local Kiro credential stores:

```bash
rotom login --kiro
```

The Kiro portal callback looks like
`http://localhost:3128/oauth/callback?login_option=google&code=...&state=...`.
rotom exchanges it with Kiro's desktop auth service using PKCE and saves a
normal rotom provider entry. The stored `refresh_token` value is rotom metadata
containing the Kiro refresh token, auth region, API region, profile ARN, and
User-Agent needed for refresh; raw tokens are never printed. The browser flow
currently supports Kiro social callbacks (`google` and `github`).

Explicit import from an existing official Kiro login remains available when
you intentionally want to reuse local Kiro CLI or IDE credentials:

```bash
# Prefer the Kiro CLI SQLite store when it exists:
rotom kiro import --from cli

# Or import the Kiro IDE desktop token JSON:
rotom kiro import --from desktop
```

`rotom kiro import --from auto` is opt-in. It checks the CLI store first at
`~/Library/Application Support/kiro-cli/data.sqlite3` on macOS or
`~/.local/share/kiro-cli/data.sqlite3` on Linux, then the desktop token at
`~/.aws/sso/cache/kiro-auth-token.json`. Raw Kiro tokens, client secrets, and
AWS SSO fields are never printed. Kiro credentials support import, refresh,
status, model listing, and API serving through Kiro's native event-stream
runtime.

Kiro's runtime protocol is not OpenAI or Anthropic upstream-compatible. rotom
maps compatible request shapes into Kiro's `GenerateAssistantResponse` schema:
text turns, system/developer instructions, function tools, tool results,
conversation history, inline base64 images, and inline base64 documents. Kiro
does not expose upstream fields for `temperature`, `top_p`, `max_tokens`,
`stop`, `reasoning` effort, `service_tier`, or forced `tool_choice`; rotom does
not pass those fields to Kiro. `tool_choice: "none"` is handled locally by not
sending tools. Remote image or document URLs are rejected instead of fetched by
the gateway.

Cursor login uses Cursor's browser polling flow. It prints a
`https://cursor.com/loginDeepControl?...` URL and polls Cursor for the browser
approval result; it does not start a localhost callback server:

```bash
rotom login --cursor
```

Cursor runtime support talks directly to Cursor's AgentService over
HTTPS/HTTP2 using the browser-login token. rotom does not spawn or inspect any
local Cursor binary, does not write Cursor's global
`~/.cursor/cli-config.json`, and does not grant workspace permissions. Requests
default to agent-mode AgentService runs, configurable through
`ROTOM_CURSOR_AGENT_MODE`.

Cursor is an agent runtime, not a plain model API, so client-side tools are
bridged rather than executed by rotom. Tools supplied in OpenAI/Anthropic
requests are advertised to Cursor as an in-process MCP server (`rotom-tools`).
When the model calls one, rotom surfaces it back as a standard
`tool_call`/`tool_use` item so the calling agent (Claude Code, etc.) executes it
locally, then feeds the returned tool result into the same AgentService stream.
Cursor also injects its own built-in agent tools (shell, read, list, grep, edit,
write, ...) that the bridge cannot run; rotom tells the model it operates in a
remote environment where those built-ins are unavailable, and if the model still
calls one, rotom returns a declined tool result steering it back to the bridged
tools. Multimodal inputs are rejected instead of being silently dropped, and
sampling/output-length controls are not forwarded because AgentService exposes
no equivalent fields.

## Use With SDKs

OpenAI-compatible chat request:

```bash
curl http://127.0.0.1:14550/v1/chat/completions \
  -H 'content-type: application/json' \
  -d '{
    "model": "gpt-5.5",
    "messages": [{"role": "user", "content": "hello"}]
  }'
```

Anthropic-compatible Messages request:

```bash
curl http://127.0.0.1:14550/v1/messages \
  -H 'content-type: application/json' \
  -H 'x-api-key: local-secret' \
  -H 'anthropic-version: 2023-06-01' \
  -d '{
    "model": "gpt-5.5",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "hello"}]
  }'
```

## Runtime Options

Optional local API key protection:

```bash
ROTOM_API_KEY=local-secret rotom serve
curl http://127.0.0.1:14550/v1/models -H 'authorization: Bearer local-secret'
```

Interactive runtime configuration:

```bash
rotom config
rotom config show
rotom config reset
```

The config file is stored at `~/.rotom/config.json` by default and is used as
the fallback source for `rotom serve` and `rotom daemon install`.

Update to the latest published release:

```bash
rotom update
```

rotom defaults unsupported Anthropic-native model ids such as
`claude-sonnet-*` to `gpt-5.5`. To override that fallback explicitly:

```bash
ROTOM_MODEL_FALLBACK=gpt-5.5 rotom serve --api-key local-secret
```

rotom rewrites known unsupported Anthropic model ids to the effective
fallback before calling Codex. When you do not configure one explicitly, the
default fallback is `gpt-5.5`.

Common pitfalls:

- Do not set `ANTHROPIC_BASE_URL` to `http://127.0.0.1:14550/v1`; Claude
  Code appends `/v1/messages` itself.
- Use a model that `/v1/models` actually returns, such as `gpt-5.5`. If Claude
  Code defaults to `claude-sonnet-*`, the request will fail because rotom
  proxies Codex models, not Anthropic-hosted model IDs.
- `ANTHROPIC_AUTH_TOKEN` is only the local gateway key configured with
  `--api-key`; it is not your upstream OpenAI/Codex OAuth token.
- If you prefer a background service, install the daemon first and then point
  `ANTHROPIC_BASE_URL` at the daemon address instead of running `rotom serve`
  manually.

Refresh stored OAuth tokens while the server is running:

```bash
curl -X POST http://127.0.0.1:14550/v1/auth/refresh \
  -H 'authorization: Bearer local-secret'
```

From the CLI, `rotom refresh` refreshes all saved providers. Use
`rotom refresh --provider grok`, `rotom refresh --provider kiro`, or
`rotom refresh --provider cursor` to refresh only one provider.

Check the rotom version, token expiry, authentication status, strongest
highlight models, and daemon endpoints:

```bash
rotom status
rotom status --provider grok
rotom status --provider kiro
rotom status --provider cursor
```

By default, `rotom status` reports every saved provider. Use `--provider` to
inspect only one provider. The highlight list is selected from each provider's
available models by model family, version number, and capability suffix, so
live Cursor credentials can affect the Cursor highlights. When the daemon
responds on the configured bind address, `rotom status` also prints the local
API endpoint URLs.

List all model ids rotom exposes:

```bash
rotom models
rotom models --provider codex
rotom models --provider grok
rotom models --provider kiro
rotom models --provider cursor
```

`rotom models` is the full registry view. It works offline for built-in
provider lists. When Cursor credentials are available, it fetches Cursor's live
model registry; otherwise Cursor falls back to compatibility aliases.

Fetch status data over HTTP:

```bash
curl http://127.0.0.1:14550/v1/status \
  -H 'authorization: Bearer local-secret'
```

Example fields:

```json
{
  "account_id": "acc_123",
  "token": {
    "expires_at_local": "2026-05-05 12:15:07 +08:00",
    "remaining_seconds": 813427
  },
  "account": {
    "email": "user@example.com",
    "plan": "chatgptpro",
    "has_active_subscription": true
  }
}
```

Install rotom as a per-user background daemon:

```bash
rotom daemon install
rotom daemon reinstall
rotom daemon start
rotom daemon status
rotom daemon restart
rotom daemon stop
rotom daemon uninstall
```

On macOS, rotom installs a LaunchAgent at
`~/Library/LaunchAgents/com.rotom.daemon.plist`. On Linux, it installs a
systemd user unit at `~/.config/systemd/user/rotom.service`.
Windows does not currently implement native daemon/service management; use WSL
and run the Linux build there if you need `rotom daemon` commands.

On Linux, inspect the per-user service with:

```bash
rotom daemon status
systemctl --user status rotom.service
```

The daemon runs `rotom serve` with the options passed at install time. If
`--api-key` is provided, rotom stores it in `~/.rotom/config.json` instead of
embedding the secret in the launchd/systemd service definition:

```bash
rotom daemon install \
  --bind 127.0.0.1:14550 \
  --api-key local-secret
```

The daemon accepts the same comma-separated bind list and CIDR selectors as
`rotom serve`, for example `--bind 127.0.0.1:14550,192.168.1.0/24:14550`.

Models returned by `/v1/models` include the OpenAI/Codex registry:

```text
gpt-5.1
gpt-5.1-codex-max
gpt-5.1-codex-mini
gpt-5.2
gpt-5.2-codex
gpt-5.3-codex
gpt-5.3-codex-spark
gpt-5.4
gpt-5.4-mini
gpt-5.5
```

and the Grok registry:

```text
grok-4.3
grok-4.3-fast
grok-4
```

Kiro exposes the validated Kiro model registry:

```text
auto
claude-opus-4.8
claude-opus-4.7
claude-opus-4.6
claude-sonnet-4.6
claude-opus-4.5
claude-sonnet-4.5
claude-sonnet-4
claude-haiku-4.5
deepseek-3.2
minimax-m2.5
minimax-m2.1
glm-5
qwen3-coder-next
```

Cursor model availability is account-dependent. `rotom models --provider
cursor` fetches the live Cursor model registry after login; without Cursor
credentials it falls back to these compatibility aliases:

```text
cursor/auto
cursor/gpt-5
cursor/sonnet-4
cursor/sonnet-4-thinking
```

Credentials are stored at `~/.rotom/auth.json` by default. Override with
`--auth-file`, `ROTOM_AUTH_FILE`, or `ROTOM_HOME`.

Runtime config supports `model_fallback`, and the CLI accepts
`--model-fallback` / `ROTOM_MODEL_FALLBACK`. When unset, rotom defaults the
fallback to `gpt-5.5`.

OpenAI compatibility currently covers:

- `GET /v1/models`
- `POST /v1/chat/completions`
- `POST /v1/responses`
- `GET /v1/responses/{response_id}`
- `DELETE /v1/responses/{response_id}`
- `POST /v1/responses/{response_id}/cancel`
- `GET /v1/responses/{response_id}/input_items`
- `POST /v1/images/generations`
- `POST /v1/responses/compact`
- `POST /v1/responses/input_tokens`

On `POST /v1/chat/completions`, rotom accepts common OpenAI compatibility
fields such as `temperature`, `max_tokens`, `max_completion_tokens`, and
`max_output_tokens`, but the current Codex upstream rejects those parameters.
rotom therefore accepts them without error and omits them from upstream Codex
requests, so they should be treated as Codex compatibility no-ops rather than
effective sampling or output-length controls. Grok Responses requests preserve
the supported controls that xAI accepts, including `temperature`, `top_p`,
`max_output_tokens`, `stop`, `text`, `include`, and `parallel_tool_calls`.
Cursor requests are adapted directly to Cursor AgentService's HTTP/2 Connect
protocol; unsupported controls are not forwarded. OpenAI/Anthropic client tools
are bridged to Cursor through an in-process `rotom-tools` MCP server and surfaced
back as standard tool calls for the client to execute (see the Cursor runtime
notes above). Cursor's own built-in agent tools are not executed by rotom:
built-in `exec_server_message` tools (shell, read, list, grep, write, ...) are
declined with guidance that steers the model back to the bridged tools, while
interactive queries such as `ask_question` and `web_fetch_request` are rejected.
Multimodal content is rejected unless it can be represented safely in
text-only agent input. rotom defaults Cursor requests to `agent.v1.AgentMode`
`AGENT (1)` instead of `ASK (2)`. You can override that with
`ROTOM_CURSOR_AGENT_MODE=agent|ask|plan|debug|triage|project|multitask` (or
`0-7`) when you need to probe a specific Cursor mode.

`/v1/responses` uses provider-specific creation paths. Codex keeps the
historical local replay behavior for existing OpenClaw and agent clients, so
simple Codex requests receive rotom-local response ids and can be retrieved,
deleted, and listed for input items while the same rotom process remains alive.
Grok requests use xAI's native Responses create API, return upstream response
ids, and mirror stored responses locally for compatibility. Grok
`previous_response_id` is forwarded to xAI when it points at an upstream-backed
response; local-only history is still replayed by rotom. Grok retrieve/delete
is forwarded to xAI for unknown ids, and upstream-backed local Grok records are
deleted upstream before the local mirror is removed. Codex resource lifecycle
calls are not claimed as upstream-supported unless the ChatGPT Codex backend
adds that API.

Image generation is exposed in two compatibility shapes:

- OpenAI-style `POST /v1/images/generations`
- OpenAI Responses hosted tool `{"type":"image_generation"}`

Current image-generation caveats:

- OpenAI `POST /v1/responses` supports streaming image-generation events
- `POST /v1/images/generations` remains non-streaming
- Anthropic `POST /v1/messages` image generation streaming is exposed as a
  rotom extension that emits `image` content blocks only once the upstream
  response completes
- generated images are returned as base64 payloads
- Anthropic compatibility uses a rotom extension that returns
  `content: [{"type":"image","source":{"type":"base64",...}}]` on
  `POST /v1/messages` when the request includes a tool named `image_generation`

Anthropic compatibility currently covers:

- `GET /v1/models` with an `anthropic-version` header
- `POST /v1/messages`
- `POST /v1/messages/count_tokens`
- `POST /v1/messages/batches`
- `GET /v1/messages/batches`
- `GET /v1/messages/batches/{batch_id}`
- `POST /v1/messages/batches/{batch_id}/cancel`
- `DELETE /v1/messages/batches/{batch_id}`
- `GET /v1/messages/batches/{batch_id}/results`
- `x-api-key` or `authorization: Bearer ...` local auth
- Anthropic-style SSE events for streaming text and tool use

Message batches execute asynchronously in a background task. Cancellation is
best-effort at request boundaries inside the batch worker: requests that have
already started are allowed to finish, while not-yet-started requests are
marked as `canceled`.

The implementation intentionally follows Ollama's compatibility strategy where
possible: Anthropic headers are accepted, locally configured auth is enforced,
and unsupported advanced Anthropic-only features are ignored rather than
rejected when possible.

The default Codex OAuth flow follows OpenClaw/pi-ai's Codex flow: PKCE, manual
paste of the `http://localhost:1455/auth/callback?...` redirect URL, token
exchange at `https://auth.openai.com/oauth/token`, and Codex requests to
`https://chatgpt.com/backend-api/codex/responses`. Grok OAuth uses xAI OIDC
discovery, PKCE, manual callback paste, and xAI Responses requests under
`https://api.x.ai/v1`. Kiro OAuth uses PKCE with `https://app.kiro.dev/signin`
and exchanges social callbacks at
`https://prod.us-east-1.auth.desktop.kiro.dev/oauth/token`. Cursor login uses
`https://cursor.com/loginDeepControl` with polling at
`https://api2.cursor.sh/auth/poll`, and runtime calls use Cursor
AgentService's HTTP/2 Connect protocol directly.

## Disclaimer

rotom is an unofficial compatibility tool. It is not affiliated with,
endorsed by, or supported by OpenAI, Anthropic, xAI, Kiro, or Cursor.

You are responsible for making sure your usage complies with the terms,
policies, account restrictions, and data-handling obligations that apply to
your upstream account and deployment environment. In particular, do not assume
that personal OAuth-backed access can be shared, resold, or safely exposed as a
multi-user hosted service. The LGPLv3 license for this repository does not
change those upstream restrictions.

## License

Copyright (c) 2026 rotom contributors.

Licensed under the GNU Lesser General Public License v3.0 only. See [LICENSE](LICENSE).