Metadata-Version: 2.5
Name: gptmock
Version: 2026.9.29
Summary: OpenAI & Ollama compatible API powered by your ChatGPT account
Project-URL: Homepage, https://github.com/rapidrabbit76/GPTMock
Project-URL: Repository, https://github.com/rapidrabbit76/GPTMock
Project-URL: Issues, https://github.com/rapidrabbit76/GPTMock/issues
License-Expression: MIT
License-File: LICENSE
Keywords: api,chatgpt,gpt5,ollama,openai,proxy
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.13
Requires-Dist: certifi==2025.8.3
Requires-Dist: click==8.2.1
Requires-Dist: fastapi>=0.104.0
Requires-Dist: httpx>=0.25.0
Requires-Dist: idna==3.10
Requires-Dist: openai>=2.21.0
Requires-Dist: pydantic-settings>=2.0.0
Requires-Dist: uvicorn[standard]>=0.24.0
Description-Content-Type: text/markdown

<p align="center">
  <img src="assets/banner.png" alt="GPTMock banner" />
</p>

<h1 align="center">GPTMock</h1>

<p align="center"><strong>OpenAI &amp; Ollama compatible API powered by your ChatGPT account.</strong></p>

<p align="center">
  <a href="https://github.com/rapidrabbit76/GPTMock"><img alt="Tests" src="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/rapidrabbit76/255a945245d92c731d002ee3be93a74c/raw/gptmock-tests.json"></a>
  <a href="https://github.com/rapidrabbit76/GPTMock"><img alt="Coverage" src="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/rapidrabbit76/255a945245d92c731d002ee3be93a74c/raw/gptmock-coverage.json"></a>
  <a href="https://www.python.org/downloads/"><img alt="Python 3.13+" src="https://img.shields.io/badge/python-3.13%2B-blue.svg"></a>
  <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-green.svg"></a>
</p>

> **This is a fork of [RayBytes/chatmock](https://github.com/RayBytes/chatmock).**
> The original Flask + synchronous `requests` stack has been replaced with **FastAPI + async `httpx`**, a layered architecture (router / service / infra), `pydantic-settings` configuration, and `uv` as the build system.

Integration and coverage badges are updated from local runs. Refresh both by running `scripts/test.sh` with `GIST_TOKEN` available in your environment or `.env`.

GPTMock runs a local protocol adapter in front of the ChatGPT Codex backend. OpenAI SDKs, OpenAI-compatible frontends and gateways, and Ollama-compatible clients can use the same authenticated backend without GPTMock pretending that remote models are local weights. Its model catalog is a configured snapshot, not a live availability check. Backend access can change before that catalog is updated; see [Supported Models](#supported-models) for dated results and known stale entries.

GPTMock adapts client requests to the backend's supported format while preserving model identity, instruction text, strict function schemas, tool choice, reasoning controls, and service-tier requests. For GPT-5.3 Codex Spark, GPT-5.5, GPT-5.6 Luna/Terra/Sol, and GPT-6 Astra, system-message text is carried in the Responses `instructions` field so existing chat clients can keep their system prompts. The actual response model, service tier, terminal status, and remaining upstream errors are returned to the client.

> **Migration note:** `--reasoning-compat` now defaults to `standard`, which emits reasoning via `delta.reasoning_content` / `message.reasoning_content` instead of injecting `<think>` tags into `content`. Set `--reasoning-compat think-tags` (or `GPTMOCK_REASONING_COMPAT=think-tags`) to keep the old behavior.

## Requirements

- **Docker Engine 24+ with Docker Compose v2** (recommended deployment)
- **Paid ChatGPT account** (Plus / Pro / Team / Enterprise)
- **Python 3.13+** and [`uv`](https://docs.astral.sh/uv/getting-started/installation/) only for direct, non-Docker usage

---

## Quick Start (Docker, recommended)

The standard `docker-compose.yml` uses `rapidrabbit76/gptmock:latest`, the `./volumes/gptmock` bind mount, and requires a local `.env` file. Its published ports bind to all host interfaces.

The commands below explicitly select `docker-compose.local.yml`, a separate configuration that builds the checked-out source and applies the hardened runtime settings. It does not overwrite or merge into `docker-compose.yml`. When testing an unmerged pull request, check out its branch before building; the published image and PyPI package may not contain those changes yet.

### 1. Clone and build

```bash
git clone https://github.com/rapidrabbit76/GPTMock.git
cd GPTMock
docker compose -f docker-compose.local.yml build
```

### 2. Start (first run — login + serve in one step)

Run the container interactively. If no credentials are found, the login flow starts automatically:

```bash
docker compose -f docker-compose.local.yml run --rm --service-ports serve
```

A URL will be printed in the terminal:

```
No credentials found. Starting login flow...
Starting local login server on http://localhost:1455
If your browser did not open, navigate to:
  https://auth.openai.com/oauth/authorize?...

If the browser can't reach this machine, paste the full redirect URL here and press Enter:
```

**Two ways to complete login:**

1. **Browser on the same machine** — the URL opens automatically and the OAuth callback is caught on port 1455.
2. **Browser on a different machine** — open the URL, complete login, then copy the full redirect URL from the browser address bar (starts with `http://localhost:1455/auth/callback?code=...`) and paste it into the terminal.

Once login succeeds, the server starts automatically.

### 3. Subsequent starts

Once credentials are saved in the volume, just run in the background:

```bash
docker compose -f docker-compose.local.yml up -d serve
```

`docker-compose.local.yml` uses the named volume `gptmock-data`; the original `docker-compose.yml` continues to use `./volumes/gptmock`. The two configurations have separate credential storage. Log in once for the selected configuration, or explicitly migrate existing credentials before switching.

### 4. Verify

```bash
curl -s http://localhost:8000/health | jq .
```

### Docker Environment Variables

All server options below are also available as environment variables. Use the `GPTMOCK_*` canonical names (see [Server Options](#server-options)).

Additional Docker-specific variables:

| Variable | Default | Description |
|----------|---------|-------------|
| `GPTMOCK_HOME` | `/data` | Auth file directory — mount a volume here |
| `GPTMOCK_LOGIN_BIND` | `0.0.0.0` | OAuth callback server bind address |
| `GPTMOCK_API_KEY` | unset | Optional Bearer token required by `/v1/*` and `/api/*` routes |
| `GPTMOCK_OUTPUT_TOKEN_POLICY` | `omit` | Handle unenforceable client output limits: `omit` with warning/header or `reject` with HTTP 400 |
| `GPTMOCK_OLLAMA_VERSION` | `0.12.10` | Ollama API compatibility header version |

In `docker-compose.local.yml`, the published ports bind to host loopback. If you expose them on a LAN, configure `GPTMOCK_API_KEY` and an explicit `GPTMOCK_CORS_ORIGINS` allowlist first.

### Docker Security Defaults

The separate `docker-compose.local.yml` deployment runs as UID/GID `10001`, mounts the root filesystem read-only, drops every Linux capability, enables `no-new-privileges`, applies a PID limit, and persists credentials only in the `gptmock-data` volume. Ports `8000` and `1455` are published to host loopback only.

API authentication is optional for local loopback use. To require a Bearer token, copy `.env.example` to `.env` and set:

```dotenv
GPTMOCK_API_KEY=replace-with-a-long-random-value
GPTMOCK_CORS_ORIGINS=
```

Clients must then send `Authorization: Bearer <GPTMOCK_API_KEY>` to `/v1/*` and `/api/*`. The health endpoint remains unauthenticated for container health checks. Browser CORS access stays disabled until an explicit origin allowlist is configured.

---

## Direct Install (uvx)

This is the non-Docker development path. No clone or persistent installation is needed.

### 1. Login

```bash
uvx gptmock login
```

A browser window will open for ChatGPT OAuth. After login, tokens are saved to `~/.config/gptmock/auth.json`.

### 2. Start the server

```bash
uvx gptmock serve
```

The server starts at `http://127.0.0.1:8000`. Use `http://127.0.0.1:8000/v1` as your OpenAI base URL.

### 3. Verify

```bash
uvx gptmock info
```

### Tip: Shell Alias

```bash
alias gptmock='uvx gptmock'

gptmock login
gptmock serve --port 9000
gptmock info
```

> **Note:** To install directly from the GitHub repository instead of PyPI:
> ```bash
> uvx --from "git+https://github.com/rapidrabbit76/GPTMock" gptmock login
> uvx --from "git+https://github.com/rapidrabbit76/GPTMock" gptmock serve
> ```

---

## Usage Examples

Use an explicit model that is currently available to your ChatGPT account. The basic text examples below use `gpt-5.6-luna`, which completed direct Chat and Responses requests in the 2026-09-13 check. This does not establish every SDK or tool workflow. For authenticated deployments, add `Authorization: Bearer <GPTMOCK_API_KEY>` to the curl examples and use the configured key in SDK clients.

### Existing Docker volume migration

The image runs as UID/GID `10001:10001`. The original Compose bind mount can contain files written by an older root-running image; image-layer ownership does not change host bind-mount permissions. Back up the credential directory and stop GPTMock before migrating it. On a Linux host, from the verified GPTMock checkout directory:

```bash
realpath ./volumes/gptmock
sudo chown -R 10001:10001 ./volumes/gptmock
sudo chmod 700 ./volumes/gptmock
sudo chmod 600 ./volumes/gptmock/auth.json  # if credentials already exist
```

Apply ownership changes only to that credential directory, not the repository or a broader parent directory. Do not use world-writable permissions. Docker Desktop bind-mount permission behavior can differ; the separate local Compose named volume is the recommended fresh-install path. Login and serve check storage access before starting and report migration guidance if it is inaccessible. Concurrent credential refreshes sharing one auth home are serialized using a local filesystem lock; avoid simultaneous manual re-login and refresh or network filesystems without reliable locking.

### OpenCode

Use OpenCode's Responses provider as the recommended configuration. OpenCode 1.x configuration:

```json
{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "gptmock": {
      "npm": "@ai-sdk/openai",
      "name": "GPTMock",
      "options": {
        "baseURL": "http://127.0.0.1:8000/v1",
        "apiKey": "replace-with-your-GPTMOCK_API_KEY"
      },
      "models": {
        "gpt-5.6-luna": {
          "name": "GPT-5.6 Luna",
          "reasoning": true,
          "tool_call": true,
          "variants": {
            "none": { "reasoningEffort": "none" },
            "low": { "reasoningEffort": "low" },
            "medium": { "reasoningEffort": "medium" },
            "high": { "reasoningEffort": "high" },
            "xhigh": { "reasoningEffort": "xhigh" },
            "max": { "reasoningEffort": "max" }
          }
        }
      }
    }
  }
}
```

Select `gptmock/gpt-5.6-luna` and the desired variant. OpenCode automatically sends `max_output_tokens`; GPTMock's default `omit` policy keeps the request compatible while explicitly reporting that the limit was not enforced.

For `gpt-5.3-codex-spark`, `gpt-5.5`, the GPT-6 Astra/Sol/Luna models, and the GPT-5.6 Luna/Terra/Sol models, GPTMock's Chat endpoint moves text system messages into upstream `instructions`. OpenCode 1.17.18 completed a real Astra file-read tool call and its follow-up through both adapters. The other listed models use the same system-message adaptation; that does not establish full client/tool compatibility for every model. SDK versions may encode Responses prompts as developer input or as `instructions`.

To request maximum Astra reasoning, select `gpt-6-astra` with `--variant max` (provider option `reasoningEffort: "max"`). `gpt-6-astra-max` is not a model: that removed alias returns HTTP 400 with migration guidance instead of silently selecting another effort. `max` remains supported through the reasoning parameter.

### Troubleshooting: `System messages are not allowed`

Older GPTMock builds adapted text system messages only for Astra, and the first extension covered GPT-5.6 Luna/Terra/Sol. Chat clients sending system prompts to GPT-5.5 or GPT-5.3 Codex Spark could still receive HTTP 400 from the backend. The adapter now covers all six concrete models, the `gpt-5.6` alias, and their existing `*-fast` requests. No Spark fast alias is added. Existing `instructions` and system text are retained in order; developer messages and tool history remain unchanged.

If this error persists, verify that the running GPTMock container includes the fix, not just that the source checkout was updated. Rebuild and recreate the GPTMock service using its existing deployment configuration and credential storage. When a gateway sits between the client and GPTMock, compare a small system-plus-user request directly against GPTMock and through the gateway before changing the client's API mode or model. A successful direct request does not by itself verify the complete Hermes or other agent workflow.

The regression tests in [`tests/test_astra.py`](tests/test_astra.py) cover Chat Completions, Responses, Ollama chat/generate, fast aliases, and streaming/non-streaming requests using a simulated upstream. Live client verification is separate from these tests.

**GPT-5.5 and Spark verification (2026-09-13):** both models rejected literal system messages through an unpatched Chat route, but completed non-streaming developer-message Chat requests and streaming/non-streaming Responses requests using `instructions`. After rebuilding and recreating the Docker service with the extended adapter, direct Chat requests for GPT-5.5, its existing fast alias, and Spark passed all six streaming/non-streaming checks with system, developer, and user input. Each returned HTTP 200, the expected concrete model, and the expected test marker. One earlier Spark streaming sample through the local adapter did not exactly match the requested text; follow-up and deployed checks matched. These results verify the deployed GPTMock Chat path, not an end-to-end Hermes/OpenCode workflow or LiteLLM configuration changes. Do not discard system prompts or silently substitute another model to hide this error.

### Python (OpenAI SDK)

```python
from openai import OpenAI

client = OpenAI(
    base_url="http://127.0.0.1:8000/v1",
    api_key="gptmock-local"  # use GPTMOCK_API_KEY when it is configured
)

resp = client.chat.completions.create(
    model="gpt-5.6-luna",
    messages=[{"role": "user", "content": "hello world"}]
)
print(resp.choices[0].message.content)
```

### Python (LangChain)

```python
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    base_url="http://127.0.0.1:8000/v1",
    api_key="gptmock-local",  # use GPTMOCK_API_KEY when it is configured
    model="gpt-5.6-luna",
)
response = llm.invoke("hello world")
print(response.content)
```

### curl

```bash
curl http://127.0.0.1:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.6-luna",
    "messages": [{"role": "user", "content": "hello world"}]
  }'
```

### Image Generation (Responses API)

GPTMock can expose the ChatGPT Codex backend's built-in image generation tool through `POST /v1/responses`. This uses your existing GPTMock / Codex OAuth credentials; no separate OpenAI API key is required.

The original examples used `gpt-5.4` and `gpt-5.4-mini`; both were rejected by the tested ChatGPT Codex backend on 2026-09-13. Image generation was not revalidated with a replacement model. The following illustrates the request format only: replace `<verified-image-generation-model>` with a model whose image-generation tool access you have verified for your account. A successful text response alone does not verify image generation.

Pass an `image_generation` tool in the Responses API request:

```bash
curl http://127.0.0.1:8000/v1/responses \
  -H "Content-Type: application/json" \
  -d '{
    "model": "<verified-image-generation-model>",
    "input": [
      {
        "type": "message",
        "role": "user",
        "content": [
          {
            "type": "input_text",
            "text": "Use the image_generation tool to create a cute illustration of a fluffy orange tabby cat sitting on a white background. Return only the generated image."
          }
        ]
      }
    ],
    "tools": [{"type": "image_generation", "output_format": "png"}],
    "tool_choice": "auto",
    "stream": false
  }'
```

For non-streaming requests, generated images are returned as `image_generation_call` items in `output`. The `result` field is a base64-encoded PNG payload:

```json
{
  "output": [
    {
      "type": "message",
      "status": "completed",
      "role": "assistant",
      "content": [{"type": "output_text", "text": ""}]
    },
    {
      "type": "image_generation_call",
      "id": "ig_...",
      "status": "generating",
      "output_format": "png",
      "revised_prompt": "A cute illustration of a fluffy orange tabby cat...",
      "result": "<base64 png>"
    }
  ]
}
```

Given the parsed response JSON, find an image result by type rather than assuming a fixed output position:

```python
import base64

image_b64 = next(
    (item["result"] for item in response.get("output", [])
     if item.get("type") == "image_generation_call" and item.get("result")),
    None,
)
if image_b64 is None:
    raise RuntimeError("The response did not contain a generated image")
with open("cat.png", "wb") as fp:
    fp.write(base64.b64decode(image_b64))
```

> **Notes:** Image-generation support is account- and backend-dependent. Earlier documentation reported successful GPT-5.4/mini image generation, but that is historical evidence, not current availability.

### Local Image Inspection (`view_image`)

GPTMock also supports a Codex-compatible `view_image` client-side tool for `POST /v1/responses`. Unlike `image_generation`, this is not executed by the upstream backend: GPTMock reads the local file, returns it to the model as an `input_image` function-call output, and then continues the Responses turn.

Enable it per request by passing the shorthand tool. This is a request-format example, not a fresh live validation: replace `<verified-image-input-model>` with a currently available model verified for image input and function calling, and use an image path readable by the GPTMock server:

```bash
curl http://127.0.0.1:8000/v1/responses \
  -H "Content-Type: application/json" \
  -d '{
    "model": "<verified-image-input-model>",
    "input": [
      {
        "type": "message",
        "role": "user",
        "content": [
          {
            "type": "input_text",
            "text": "Use view_image to inspect this local image path: /images/example.png. Describe it briefly."
          }
        ]
      }
    ],
    "tools": [{"type": "view_image"}],
    "tool_choice": "auto",
    "stream": false
  }'
```

The shorthand is normalized to a Responses function tool named `view_image`. You can also provide an explicit function tool with the same name.

**Docker:** paths are resolved inside the container, not on the client or host. The image does not include the repository's `assets/banner.png`. Mount your image directory read-only at `/images` alongside the existing credential volume, set `GPTMOCK_VIEW_IMAGE_ROOTS=/images`, and ensure the container's non-root user can read the selected file. Merely setting the allowed roots does not mount or copy any files.

By default, `view_image` can read files under the server's current working directory only. Configure the readable roots when needed:

```bash
GPTMOCK_VIEW_IMAGE_ROOTS="/path/to/images:/another/root" gptmock serve
```

Additional knobs:

| Variable | Default | Description |
|----------|---------|-------------|
| `GPTMOCK_VIEW_IMAGE_ROOTS` | server cwd | `os.pathsep`-separated list of readable image roots |
| `GPTMOCK_VIEW_IMAGE_ALLOW_ANY_PATH` | off | Set to `1` to allow any local path readable by the server process |
| `GPTMOCK_VIEW_IMAGE_MAX_BYTES` | `20971520` | Maximum image file size in bytes |

Supported image content types are PNG, JPEG, GIF, and WebP. `detail: "original"` is accepted when the model requests original-resolution handling.

---

## Supported Models

The following snapshot combines the current registry with direct backend checks on **2026-09-13 (KST)**. The availability check used `low` effort, non-streaming, user-only Chat Completions and Responses requests. Successful responses contained the requested test text and the expected concrete model name. It did not rerun every reasoning level, tools, images, or client integration. The effort column describes GPTMock's configured validation range, not fresh verification of every level.

| Model | Configured Reasoning Efforts | Observed Backend Status (2026-09-13) |
|-------|-------------------|--------|
| `gpt-5.3-codex-spark` | `low` / `medium` / `high` / `xhigh` | ✅ Text and `instructions` requests completed; system text is adapted by GPTMock |
| `gpt-5.5` | `low` / `medium` / `high` / `xhigh` | ✅ Text and `instructions` requests completed; system text is adapted by GPTMock |
| `gpt-5.6` | `none` / `low` / `medium` / `high` / `xhigh` / `max` | ✅ Completed as `gpt-5.6-sol` |
| `gpt-5.6-sol` | `none` / `low` / `medium` / `high` / `xhigh` / `max` | ✅ Basic text requests completed |
| `gpt-5.6-terra` | `none` / `low` / `medium` / `high` / `xhigh` / `max` | ✅ Basic text requests completed |
| `gpt-5.6-luna` | `none` / `low` / `medium` / `high` / `xhigh` / `max` | ✅ Basic text requests completed |
| `gpt-6-astra` | `low` / `medium` / `high` / `xhigh` / `max` | ✅ Basic text requests completed |

**GPT-6 Sol and Luna (added 2026-09-29):** `gpt-6-sol` and `gpt-6-luna` are also advertised by `/v1/models` and `/api/tags` and route to their exact upstream IDs. Both accept `none`, `low`, `medium` (default), `high`, `xhigh`, and `max`, matching the official [Sol](https://developers.openai.com/api/docs/models/gpt-6-sol) and [Luna](https://developers.openai.com/api/docs/models/gpt-6-luna) model documentation. Their `*-fast` aliases request `service_tier="priority"` without appearing in discovery. With reasoning models exposed, effort aliases such as `gpt-6-sol-max` and `gpt-6-luna-none` are available; Astra's removed `-max` alias remains a separate restriction.

Sol and Luna use the existing Responses-backed adapters for OpenAI Chat/Completions/Responses and Ollama chat/generate. Tests cover discovery, reasoning validation, exact upstream model selection, fast aliases, and streaming/system-message adaptation with a simulated upstream. They were not part of the September 13 backend snapshot above, and account availability, live tool calls, and system-role acceptance have not been verified against the ChatGPT backend.

**Removed GPT-5.4 models:** `gpt-5.4` and `gpt-5.4-mini` returned `The '…' model is not supported when using Codex with a ChatGPT account.` in the 2026-09-13 check. They and their effort variants are no longer advertised by `/v1/models` or `/api/tags`; their fast aliases are no longer registered. `/api/show` returns HTTP 404 for these removed entries. This is a dated backend/account observation, not a claim about public OpenAI API availability.

Direct Docker probes on 2026-09-03 confirmed every listed GPT-5.6 reasoning effort (`none` through `max`) for Sol, Terra, and Luna through `/v1/responses`. All 18 requests completed successfully and returned the requested concrete model name.

> **Fast compatibility aliases:** `*-fast` names are accepted when requested directly and add `service_tier="priority"` to the same verified base-model request. They are not separate models, are not advertised by `/v1/models` or `/api/tags`, and do not guarantee priority service. New integrations should prefer the verified base model plus an explicit `service_tier="priority"` request. GPTMock returns the actual upstream `service_tier` unchanged. The seven pre-Astra aliases were accepted on 2026-08-26 with `service_tier="default"`. Astra's priority request was separately accepted on 2026-09-05 and also returned `default`; `gpt-6-astra-fast` uses that same request-only convention.

Fast aliases inherit their base model's availability. On 2026-09-13, `gpt-5.4-fast` and `gpt-5.4-mini-fast` were rejected and have since been removed from alias registration; `gpt-5.5-fast` completed as `gpt-5.5` with actual tier `default`. Recognizing an alias does not make an unavailable base model usable.

> **GPT-5.6 compatibility note:** GPTMock exposes only verified GPT-5.6 model names. `gpt-5.6` follows OpenAI's documented alias to `gpt-5.6-sol`. OpenAI documents Pro as `reasoning.mode="pro"` on the same model rather than as a `*-pro` model ID, but the ChatGPT Codex backend rejected that mode in direct probes. GPTMock therefore advertises no Pro model slug and rejects `reasoning.mode` locally instead of sending a request known to be unsupported by this upstream.

> **GPT-6 Astra:** `gpt-6-astra` connects directly to that exact upstream model. The [official model documentation](https://developers.openai.com/api/docs/models/gpt-6-astra) lists `low`, `medium`, `high`, `xhigh`, and `max`; direct ChatGPT Codex probes on 2026-09-05 completed at all five efforts and echoed the requested model and effort. That backend rejected `none`, `minimal`, `ultra`, `gpt-6-astra-pro`, and `reasoning.mode="pro"`. Those options are not advertised as supported. OpenAI Chat Completions and Ollama clients use GPTMock's existing Responses-backed adapter, including tool calls. See [Astra validation](docs/astra-validation.md) for the tested interfaces and limitations.

> **System-message compatibility:** for GPT-5.3 Codex Spark, GPT-5.5, GPT-5.6 Luna/Terra/Sol, and GPT-6 Astra/Sol/Luna, GPTMock carries text system messages into `instructions`, after any existing instructions and in message order. Developer/user messages and tool results remain in `input`. This applies to OpenAI Chat, Responses, Ollama chat, and Ollama generate's `system` field, including existing fast aliases and streaming. Sol/Luna use this as a compatibility policy; their backend requirement for it has not been verified. Existing clients can keep their system prompts. Other models and unsupported non-text system content are passed through unchanged.

> **Upstream availability note:** model availability can change independently of GPTMock releases. The older model list reflects direct probes made on 2026-08-26, GPT-5.6 was rechecked on 2026-09-03, and Astra was verified on 2026-09-05. `gpt-5`, `gpt-5.1`, `gpt-5.2`, `gpt-5-codex`, `gpt-5.1-codex`, `gpt-5.1-codex-mini`, `gpt-5.1-codex-max`, `gpt-5.2-codex`, and `gpt-5.3-codex` were rejected and are therefore not advertised.

### Deprecated / Unsupported Models

The rejected names listed above, including GPT-5.4/mini, are omitted from `/v1/models` and `/api/tags`. Explicit retired or unknown non-alias identifiers are still forwarded to upstream and its rejection is preserved, rather than being silently replaced with a different model. GPT-5.4 fast identifiers no longer receive synthetic priority translation. Certain removed aliases, such as `gpt-6-astra-max`, and unsupported reasoning options are rejected locally.

Always supply a non-empty model name. Generation requests without `model` return HTTP 422. Empty or whitespace-only model names return HTTP 400 before upstream authentication, rather than implicitly selecting GPT-5.4 or another model. An explicitly configured debug-model override remains available for diagnostics.

### Request and Response Semantics

| Input or event | GPTMock behavior |
|----------------|------------------|
| `system` and `developer` messages | For Spark, GPT-5.5, GPT-5.6 Luna/Terra/Sol, and Astra, system text is moved into `instructions` and developer messages remain in `input`; other models retain the supplied roles |
| Function tools with `strict: true` | Strict schema flag and parameters are preserved |
| `tool_choice: "required"` | Forwarded as required; never weakened to `auto` |
| Rejected tools or model options | Upstream error is returned; GPTMock does not remove tools and retry |
| `reasoning.effort` or Chat `reasoning_effort` | Validated against the selected model family and forwarded; conflicting values are rejected |
| Explicit `reasoning.mode` | Rejected locally; the connected ChatGPT Codex backend rejected Pro mode |
| `max_output_tokens`, `max_completion_tokens`, or `max_tokens` | Not forwarded because the ChatGPT Codex upstream cannot enforce them. Default `omit` mode logs a warning and returns `X-GPTMock-Omitted-Parameters`; `reject` mode returns HTTP 400 |
| `service_tier` or `*-fast` request | Requested tier is sent, while the tier actually returned by upstream is exposed unchanged |
| `response.incomplete` | Preserved by `/v1/responses`; mapped to `length` or `content_filter` for text. Incomplete tool responses produce an error in Chat/Ollama, not an executable `tool_calls` finish |
| `response.failed` or interrupted SSE | Returned as an explicit error; an interrupted stream is never converted into a successful completion |
| Upstream response model | Returned unchanged instead of being replaced with the requested alias |
| Ollama model metadata | Marked as remote with zero/empty unknown size and digest values; no GGUF, Llama family, parameter size, quantization, or local evaluation timings are fabricated |
| Ollama function calls | Arguments are native JSON objects. Streaming fragments are buffered until a valid tool-call finish; tool results are matched by name when supplied |

The GPT-5.6 alias and reasoning-effort range follow [OpenAI's GPT-5.6 model guidance](https://developers.openai.com/api/docs/guides/latest-model?model=gpt-5.6). GPTMock's accepted options are narrower because they reflect what the ChatGPT Codex backend accepted during the dated probes above, not what may be available through a separate OpenAI API account.

The default output-token policy is intentionally compatibility-oriented. OpenAI SDKs and agent frontends commonly add an output-token field even when the user did not set one. GPTMock accepts such requests but does not claim that the limit was honored: the field is omitted only from the upstream request, a warning names the omitted field, and the HTTP response exposes the same fact. Use `--output-token-policy reject` when failing closed is preferable to frontend compatibility.

Docker-backed OpenCode 1.17.18 probes on 2026-09-03 verified direct Luna/Sol/Terra model selection, `none`/`high`/`max` reasoning variants, `service_tier="priority"` request preservation, streaming, tool definitions, `tool_choice="auto"`, and a complete function-call/result/final-answer loop. The upstream response still reported the actual tier as `default`. OpenCode did not emit configured `top_p`, `tool_choice="required"`, or strict tool schemas in these runs, so those semantics remain covered by GPTMock's direct request tests rather than claimed as OpenCode-verified.

The 2026-09-05 pre-PR verification reran OpenCode against a temporary local server, including Astra, reasoning variants, tool round trips, and injected stream failures. Native Ollama SDK tool loops and direct strict/named-tool tests also passed. These live checks are separate from the Docker image/runtime checks; see [pre-PR validation](docs/pr-validation.md) for results, versions, and coverage boundaries.

### Ollama Request Semantics

GPTMock supports both `/api/chat` and `/api/generate`. The native Ollama `think` field maps string levels to reasoning effort; GPTMock additionally accepts the full model-specific effort range (`none` through `max`) and the explicit `reasoning_effort` field. `think: false` suppresses the returned thinking summary. The non-standard `service_tier: "priority"` field requests priority service, while the actual upstream tier remains authoritative. Ollama `format: "json"` and JSON-schema objects map to structured output.

`options.num_predict` cannot be enforced by the ChatGPT Codex backend, so it follows `GPTMOCK_OUTPUT_TOKEN_POLICY`: default `omit` mode returns `X-GPTMock-Omitted-Parameters: options.num_predict`, while `reject` mode returns HTTP 400. Other non-empty Ollama runtime `options` are rejected instead of being silently ignored. Generate-only `suffix`, non-empty `template`, `context`, and `raw: true` are also rejected because this upstream cannot preserve their meaning.

Docker probes on 2026-09-03 verified authenticated raw HTTP requests for tags, show, structured output, reasoning, priority tier requests, streaming, strict required tools, tool-result continuation, output-limit policy, and upstream errors. Ollama CLI 0.33.1 also completed `ollama show` and a streaming `ollama run` through the loopback-only unauthenticated container. Two CLI limitations remain explicit: the CLI did not send `OLLAMA_API_KEY` to a custom local host, and `ollama list` panicked when it encountered the intentionally empty remote-model digest. GPTMock will not fabricate a weight digest to satisfy that client assumption; use `/api/tags`, `ollama show <model>`, or an Ollama-compatible client that accepts remote metadata.

---

## API Endpoints

| Method | Path | Description |
|--------|------|-------------|
| POST | `/v1/chat/completions` | OpenAI Chat Completions (stream / non-stream) |
| POST | `/v1/completions` | OpenAI Text Completions |
| POST | `/v1/responses` | OpenAI Responses API semantics |
| GET | `/v1/models` | List configured model metadata; not a live availability check |
| GET | `/api/version` | Ollama-compatible version info |
| POST | `/api/chat` | Ollama-compatible chat |
| POST | `/api/generate` | Ollama-compatible text generation |
| POST | `/api/show` | Ollama-compatible model details |
| GET | `/api/tags` | Ollama model list |
| GET | `/health` | Health check |

---

## Features

- **Streaming & Non-streaming** — real-time SSE and buffered JSON responses, including explicit incomplete terminal states
- **Structured Output** — `response_format` with `json_schema` / `json_object` support
- **Tool / Function Calling** — including web search with URL citation annotations via `responses_tools`
- **Image Generation** — Responses API `image_generation` tool support with base64 PNG output
- **Local Image Inspection** — Codex-compatible `view_image` function tool for allowed local image paths
- **Thinking Summaries** — `standard` mode returns `reasoning_content` by default; optional `think-tags`, `o3`, and `legacy` modes support other clients
- **Responses API** — `POST /v1/responses` for LangChain and other clients that auto-route codex models
- **Ollama Compatibility** — chat and generate APIs with remote-model metadata, without fabricated GGUF sizes, digests, or local evaluation timings
- **Auto Token Refresh** — JWT tokens are refreshed automatically before expiry

---

## Server Options

```
gptmock serve [OPTIONS]
```

Each option can also be set via environment variable. Precedence: **CLI flag > `GPTMOCK_*` env > `CHATGPT_LOCAL_*` legacy env > default**.

| Option | Env var | Default | Description |
|--------|---------|---------|-------------|
| `--host` | `GPTMOCK_HOST` | `127.0.0.1` | Bind address |
| `--port` | `GPTMOCK_PORT` | `8000` | Bind port |
| `--verbose` | `GPTMOCK_VERBOSE` | off | Log request/response payloads |
| `--verbose-obfuscation` | `GPTMOCK_VERBOSE_OBFUSCATION` | off | Also dump raw SSE/obfuscation events |
| `--debug-model` | `GPTMOCK_DEBUG_MODEL` | — | Force all requests to use this model name |
| `--reasoning-effort` | `GPTMOCK_REASONING_EFFORT` | `medium` | `none` / `minimal` / `low` / `medium` / `high` / `xhigh` / `max`; availability is model-specific |
| `--reasoning-summary` | `GPTMOCK_REASONING_SUMMARY` | `auto` | `auto` / `concise` / `detailed` / `none` |
| `--reasoning-compat` | `GPTMOCK_REASONING_COMPAT` | `standard` | How reasoning is exposed: `standard` / `think-tags` / `o3` / `legacy` (`openai` is accepted as an alias for `standard`, `current` as an alias for `legacy`) |
| `--expose-reasoning-models` | `GPTMOCK_EXPOSE_REASONING_MODELS` | off | Show effort variants as separate models in `/v1/models` |
| `--enable-web-search` | `GPTMOCK_DEFAULT_WEB_SEARCH` | off | Enable web search by default when `responses_tools` is omitted |
| `--output-token-policy` | `GPTMOCK_OUTPUT_TOKEN_POLICY` | `omit` | `omit` unenforceable output limits with warning/header, or `reject` them with HTTP 400 |
| `--cors-origins` | `GPTMOCK_CORS_ORIGINS` | disabled | Comma-separated allowed CORS origins |
| — | `GPTMOCK_API_KEY` | unset | Optional Bearer token for `/v1/*` and `/api/*`; strongly recommended before non-loopback exposure |

> **Legacy aliases**: `CHATGPT_LOCAL_REASONING_EFFORT`, `CHATGPT_LOCAL_REASONING_SUMMARY`, `CHATGPT_LOCAL_REASONING_COMPAT`, `CHATGPT_LOCAL_EXPOSE_REASONING_MODELS`, `CHATGPT_LOCAL_ENABLE_WEB_SEARCH`, `CHATGPT_LOCAL_DEBUG_MODEL` are still accepted as fallbacks.

---

## Web Search

Use `--enable-web-search` to enable the web search tool by default for all requests. When enabled, the model decides autonomously whether a query needs a web search. You can also enable web search per-request without the server flag by passing the parameters below.

### Request Parameters

| Parameter | Values | Description |
|-----------|--------|-------------|
| `responses_tools` | `[{"type":"web_search"}]` | Enable web search for this request |
| `responses_tool_choice` | `"auto"` / `"none"` | Let the model decide, or disable |

### Annotations (URL Citations)

When web search is active, the model may return `annotations` containing source URLs. These are included automatically in responses:

**Non-streaming** (`stream: false`) — annotations are attached to the message:

```json
{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "SpaceX launched 29 Starlink satellites...",
        "annotations": [
          {
            "type": "url_citation",
            "start_index": 0,
            "end_index": 150,
            "url": "https://spaceflightnow.com/...",
            "title": "SpaceX Falcon 9 launch"
          }
        ]
      }
    }
  ]
}
```

**Streaming** (`stream: true`) — annotations arrive as a dedicated chunk before the final `stop` chunk:

```json
data: {"choices": [{"delta": {"annotations": [{"type": "url_citation", "start_index": 0, "end_index": 150, "url": "https://...", "title": "..."}]}, "finish_reason": null}]}
data: {"choices": [{"delta": {}, "finish_reason": "stop"}]}
```

**Responses API** (`POST /v1/responses`, non-streaming) — annotations are nested inside the output content:

```json
{
  "output": [
    {
      "type": "message",
      "role": "assistant",
      "content": [
        {
          "type": "output_text",
          "text": "SpaceX launched 29 Starlink satellites...",
          "annotations": [
            {
              "type": "url_citation",
              "start_index": 0,
              "end_index": 150,
              "url": "https://spaceflightnow.com/...",
              "title": "SpaceX Falcon 9 launch"
            }
          ]
        }
      ]
    }
  ]
}
```

### Example Request

This shows the web-search request format. The basic text availability check above did not revalidate web-search entitlement or tool execution for the example model.

```bash
curl http://127.0.0.1:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.6-luna",
    "messages": [{"role":"user","content":"Find current METAR rules"}],
    "stream": true,
    "responses_tools": [{"type": "web_search"}],
    "responses_tool_choice": "auto"
  }'
```

---

## Notes & Limits

- Requires an active, paid ChatGPT account.
- Context length may be partially used by internal system instructions.
- For the lowest-reasoning latency baseline, use the lowest effort supported by the selected model (`none` for GPT-5.6, otherwise usually `low`) and set `--reasoning-summary` to `none`.
- Context limits and account entitlements are controlled by the ChatGPT Codex backend and may differ from the ChatGPT app or OpenAI API.
- When the model returns a thinking summary, the default `standard` mode emits `reasoning_content` fields without polluting `content`. Set `--reasoning-compat think-tags` to keep `<think>` tags for older chat apps, or `--reasoning-compat legacy` for the older reasoning fields.
- This project is not affiliated with OpenAI. Use responsibly and at your own risk.

## Credits

- Original project: [RayBytes/chatmock](https://github.com/RayBytes/chatmock)
- This fork: [rapidrabbit76/GPTMock](https://github.com/rapidrabbit76/GPTMock)
