mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 23:02:10 +03:00
docs: add Codex CLI configuration guide for OmniRoute
Add a comprehensive guide for configuring Codex CLI to use OmniRoute as an OpenAI-compatible backend. Document ready-to-use config examples, Responses API routing behavior, context window settings, token limits, model profiles, and troubleshooting guidance to help users avoid direct-provider compatibility issues.
This commit is contained in:
361
docs/guides/CODEX-CLI-CONFIGURATION.md
Normal file
361
docs/guides/CODEX-CLI-CONFIGURATION.md
Normal file
@@ -0,0 +1,361 @@
|
||||
# Codex CLI — Configuration with OmniRoute
|
||||
|
||||
Complete guide for using the Codex CLI pointed at OmniRoute as an OpenAI-compatible backend.
|
||||
|
||||
---
|
||||
|
||||
## Ready-to-paste config.toml
|
||||
|
||||
Replace `<YOUR_HOST>` and `<YOUR_KEY>` with your values:
|
||||
|
||||
```toml
|
||||
# ~/.codex/config.toml
|
||||
model = "cx/gpt-5.5"
|
||||
model_provider = "omniroute"
|
||||
model_reasoning_effort = "xhigh"
|
||||
model_context_window = 400000
|
||||
model_auto_compact_token_limit = 350000
|
||||
model_max_output_tokens = 65536 # max tokens per response (model cap = 128k)
|
||||
tool_output_token_limit = 32768 # history storage cap per tool call
|
||||
|
||||
[model_providers.omniroute]
|
||||
name = "OmniRoute"
|
||||
base_url = "http://<YOUR_HOST>:20128/v1"
|
||||
env_key = "OMNIROUTE_API_KEY"
|
||||
requires_openai_auth = false
|
||||
wire_api = "responses"
|
||||
```
|
||||
|
||||
```bash
|
||||
# ~/.bashrc or ~/.zshrc — actual key value, never in config.toml
|
||||
export OMNIROUTE_API_KEY="<YOUR_KEY>"
|
||||
```
|
||||
|
||||
> **Common host options**
|
||||
>
|
||||
> | Access | URL |
|
||||
> |--------|-----|
|
||||
> | Local network | `http://192.168.0.1:20128/v1` |
|
||||
> | Tailscale | `http://100.x.x.x:20128/v1` |
|
||||
> | Loopback | `http://localhost:20128/v1` |
|
||||
|
||||
---
|
||||
|
||||
## `wire_api = "responses"` — why it works for all models
|
||||
|
||||
Codex CLI deprecated `wire_api = "chat"` (Chat Completions) in February 2026 and now **requires** `wire_api = "responses"` (OpenAI Responses API).
|
||||
|
||||
DeepSeek and Mistral only expose a Chat Completions endpoint — not the Responses API. If you pointed Codex directly at DeepSeek or Mistral, it would fail with a 404.
|
||||
|
||||
**OmniRoute solves this transparently:**
|
||||
|
||||
```
|
||||
Codex CLI
|
||||
→ wire_api = "responses"
|
||||
→ POST /v1/responses (OmniRoute)
|
||||
→ OmniRoute Responses ↔ Chat Completions transformer
|
||||
→ POST /chat/completions (DeepSeek / Mistral / any provider)
|
||||
```
|
||||
|
||||
You never need a separate translation proxy (`codex-relay`, `LiteLLM`, etc.) when using OmniRoute. **All models use `wire_api = "responses"`** — OmniRoute handles the rest.
|
||||
|
||||
---
|
||||
|
||||
## Context window and compaction
|
||||
|
||||
### Why this matters
|
||||
|
||||
If the session history exceeds the model's context window, the Codex CLI either crashes or truncates silently. Different models have very different limits — setting these explicitly prevents surprises.
|
||||
|
||||
### Token configuration fields
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `model_context_window` | Total token budget for the active model. Set to the model's advertised limit. |
|
||||
| `model_auto_compact_token_limit` | Threshold that triggers automatic history compaction. **Maximum: 90% of `model_context_window`** — values above 90% are silently ignored. |
|
||||
| `model_max_output_tokens` | **Maximum tokens per response** (equivalent to Claude's `CLAUDE_CODE_MAX_OUTPUT_TOKENS`). Caps the output sent to the API on every request. Exists in CLI config since mid-2025 (Issue #4138, now fixed). |
|
||||
| `tool_output_token_limit` | Cap on tokens stored per tool call output in history. Prevents a single large tool response from filling the window. **This is not the max output** — it is a history storage cap. |
|
||||
| `compact_prompt` | Inline override for the system prompt used during compaction. |
|
||||
| `experimental_compact_prompt_file` | Load the compaction prompt from a file (experimental). |
|
||||
|
||||
> **`model_max_output_tokens` vs `tool_output_token_limit`**: these are two different things.
|
||||
> - `model_max_output_tokens` = max tokens the model may produce in a single API response.
|
||||
> - `tool_output_token_limit` = max tokens stored per tool call in the session history.
|
||||
|
||||
### Context windows and output caps by model
|
||||
|
||||
| Model | OmniRoute ID | Context window | Max output (model) | `model_max_output_tokens` | `auto_compact` | `tool_output_limit` |
|
||||
|-------|-------------|----------------|--------------------|---------------------------|----------------|----------------------|
|
||||
| GPT-5.5 | `cx/gpt-5.5` | 1,050,000 (400k reliable) | **128,000** | 65,536 | 350,000 | 32,768 |
|
||||
| DeepSeek V4 Pro | `ds/deepseek-v4-pro` | 1,000,000 | **384,000** | 65,536 | 900,000 | 65,536 |
|
||||
| Mistral Large Latest | `mistral/mistral-large-latest` | 262,144 (256k) | ~128,000 | 32,768 | 220,000 | 16,384 |
|
||||
|
||||
> **Why not set `model_max_output_tokens` to the model's maximum?**
|
||||
> For a coding assistant that writes whole files and long diffs, 64k (65,536) is a practical sweet spot. The model can generate files up to ~50k tokens without hitting the cap. Reserve the higher limits for edge cases — they increase cost on every request regardless of output length.
|
||||
|
||||
> **Compaction formula:** `effective_window = model_context_window - min(model_max_output_tokens, 20000)`. Values above 20k do not reduce the compaction trigger — the formula caps the output reservation at 20k. So setting `model_max_output_tokens = 65536` does not require lowering `model_auto_compact_token_limit`.
|
||||
|
||||
> **Rule of thumb:** set `model_auto_compact_token_limit` to 85–90% of `model_context_window`. Never go above 90% — it is silently ignored.
|
||||
|
||||
### How compaction works
|
||||
|
||||
When the session history exceeds `model_auto_compact_token_limit`, Codex CLI automatically summarises older turns into a compact form. The session continues without interruption — you lose verbatim history but keep context. This is different from truncation (which loses context).
|
||||
|
||||
For models with smaller windows (Mistral 256k), compaction fires earlier and more often. Setting a tighter `tool_output_token_limit` reduces how fast the window fills with tool call results.
|
||||
|
||||
---
|
||||
|
||||
## Model prefix: `cx/`
|
||||
|
||||
All Codex models in OmniRoute use the `cx/` prefix:
|
||||
|
||||
| Codex CLI name | OmniRoute model |
|
||||
|----------------|-----------------|
|
||||
| `cx/gpt-5.5` | GPT-5.5 standard |
|
||||
| `cx/gpt-5.4` | GPT-5.4 standard |
|
||||
| `cx/gpt-5.4-mini` | GPT-5.4 mini |
|
||||
| `cx/gpt-5.1-codex-mini` | GPT-5.1 Codex mini |
|
||||
|
||||
Other providers use their own prefix (`ds/`, `mistral/`, etc.) — the prefix matches the OmniRoute provider alias.
|
||||
|
||||
> **Never use bare `gpt-5.5` or `codex/gpt-5.5`** — OmniRoute does not recognize those formats for the Codex provider.
|
||||
|
||||
---
|
||||
|
||||
## Reasoning Effort
|
||||
|
||||
Controls how much the model "thinks" before responding. Higher effort = better quality, higher latency and cost.
|
||||
|
||||
### Available values
|
||||
|
||||
| Value | Recommended for |
|
||||
|-------|-----------------|
|
||||
| `none` | No reasoning — direct response |
|
||||
| `low` | Trivial tasks (rename a variable, format code) |
|
||||
| `medium` | **Server default** when not specified |
|
||||
| `high` | Intermediate tasks (refactoring, debugging) |
|
||||
| `xhigh` | Architecture, deep analysis, complex problems |
|
||||
|
||||
> **Note:** `model_reasoning_effort` applies to models that support reasoning (GPT-5.x, DeepSeek V4 Pro). Mistral Large does not expose a reasoning effort parameter — setting it has no effect on Mistral.
|
||||
|
||||
### How to configure
|
||||
|
||||
**In `config.toml` (global default):**
|
||||
```toml
|
||||
model_reasoning_effort = "xhigh"
|
||||
```
|
||||
|
||||
**Per invocation via `-c` (overrides global):**
|
||||
```bash
|
||||
codex -c model_reasoning_effort=low "rename variable x to count"
|
||||
codex -c model_reasoning_effort=xhigh "design the auth module architecture"
|
||||
```
|
||||
|
||||
**Combining model and effort:**
|
||||
```bash
|
||||
codex -m cx/gpt-5.4 -c model_reasoning_effort=medium "refactor the handler"
|
||||
```
|
||||
|
||||
> **About the default:** If `model_reasoning_effort` is not set, OmniRoute falls back to `"medium"`. Set it explicitly for serious engineering work.
|
||||
|
||||
---
|
||||
|
||||
## Selecting a model via the CLI
|
||||
|
||||
### 1. `--model` / `-m` flag — per invocation
|
||||
|
||||
```bash
|
||||
codex -m cx/gpt-5.5 "analyze the full pipeline"
|
||||
codex -m ds/deepseek-v4-pro "deep analysis of this algorithm"
|
||||
codex -m mistral/mistral-large-latest "quick review"
|
||||
```
|
||||
|
||||
**Priority:** CLI flags > profiles > config.toml
|
||||
|
||||
### 2. `/model` — interactive switch inside a session
|
||||
|
||||
During an open session, type `/model` + Enter to open the model picker.
|
||||
|
||||
### 3. `-c key=value` — inline override for any field
|
||||
|
||||
```bash
|
||||
# Change context window for one run
|
||||
codex -m ds/deepseek-v4-pro -c model_context_window=1000000 -c model_auto_compact_token_limit=900000 "task"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Profiles — named usage profiles
|
||||
|
||||
Profiles let you have named configurations for different workflows. Each profile is a file at `~/.codex/profile-<name>.config.toml` that overlays the base `config.toml`.
|
||||
|
||||
### How to use
|
||||
|
||||
```bash
|
||||
codex --profile deepseek "analyze 10k lines of this codebase"
|
||||
codex --profile mistral "quick code review"
|
||||
codex --profile low "rename variable"
|
||||
codex -p chat "explain this function"
|
||||
```
|
||||
|
||||
### All available profiles
|
||||
|
||||
#### `profile-chat` — no reasoning effort (server default = medium)
|
||||
```toml
|
||||
model = "cx/gpt-5.5"
|
||||
model_provider = "omniroute"
|
||||
# No model_reasoning_effort — uses server default (medium)
|
||||
```
|
||||
|
||||
#### `profile-low` / `profile-medium` / `profile-high` / `profile-xhigh`
|
||||
```toml
|
||||
model = "cx/gpt-5.5"
|
||||
model_reasoning_effort = "low" # or medium / high / xhigh
|
||||
model_provider = "omniroute"
|
||||
```
|
||||
Context window is inherited from `config.toml` (400k for gpt-5.5).
|
||||
|
||||
#### `profile-deepseek` — DeepSeek V4 Pro, 1M context
|
||||
```toml
|
||||
model = "ds/deepseek-v4-pro"
|
||||
model_provider = "omniroute"
|
||||
|
||||
model_context_window = 1000000
|
||||
model_auto_compact_token_limit = 900000
|
||||
model_max_output_tokens = 65536 # practical cap; model max = 384k
|
||||
tool_output_token_limit = 65536
|
||||
```
|
||||
|
||||
#### `profile-mistral` — Mistral Large Latest, 256k context
|
||||
```toml
|
||||
model = "mistral/mistral-large-latest"
|
||||
model_provider = "omniroute"
|
||||
|
||||
model_context_window = 262144
|
||||
model_auto_compact_token_limit = 220000
|
||||
model_max_output_tokens = 32768 # ~32k; Mistral Large model max ~128k
|
||||
tool_output_token_limit = 16384
|
||||
```
|
||||
|
||||
### Quick decision table
|
||||
|
||||
| Task | Profile |
|
||||
|------|---------|
|
||||
| Rename, format, boilerplate | `--profile low` |
|
||||
| Explain, light PR review | `--profile chat` |
|
||||
| Debug, moderate refactor | `--profile medium` |
|
||||
| New feature, complex tests | `--profile high` |
|
||||
| Architecture, system analysis | `--profile xhigh` (default) |
|
||||
| Long codebase analysis (needs 1M ctx) | `--profile deepseek` |
|
||||
| Quick tasks, cost-conscious | `--profile mistral` |
|
||||
|
||||
---
|
||||
|
||||
## Multiple models and servers
|
||||
|
||||
### Multiple models — same server
|
||||
|
||||
Change only `model` and `model_provider` (and context window fields if the model differs):
|
||||
|
||||
```toml
|
||||
model = "ds/deepseek-v4-pro"
|
||||
model_provider = "omniroute"
|
||||
model_context_window = 1000000
|
||||
model_auto_compact_token_limit = 900000
|
||||
```
|
||||
|
||||
### Multiple servers
|
||||
|
||||
```toml
|
||||
model = "cx/gpt-5.5"
|
||||
model_provider = "omniroute-main"
|
||||
|
||||
[model_providers.omniroute-main]
|
||||
name = "OmniRoute (Main)"
|
||||
base_url = "http://192.168.0.1:20128/v1"
|
||||
env_key = "OMNIROUTE_API_KEY"
|
||||
requires_openai_auth = false
|
||||
wire_api = "responses"
|
||||
|
||||
[model_providers.omniroute-tailscale]
|
||||
name = "OmniRoute (Tailscale)"
|
||||
base_url = "http://100.x.x.x:20128/v1"
|
||||
env_key = "OMNIROUTE_API_KEY"
|
||||
requires_openai_auth = false
|
||||
wire_api = "responses"
|
||||
|
||||
[model_providers.omniroute-staging]
|
||||
name = "OmniRoute (Staging)"
|
||||
base_url = "http://192.168.0.2:20128/v1"
|
||||
env_key = "OMNIROUTE_STAGING_KEY"
|
||||
requires_openai_auth = false
|
||||
wire_api = "responses"
|
||||
```
|
||||
|
||||
> All providers use `wire_api = "responses"` — OmniRoute handles translation for each upstream provider internally.
|
||||
|
||||
---
|
||||
|
||||
## Claude Code — equivalent configuration
|
||||
|
||||
Claude Code (Anthropic's CLI) uses a different mechanism for the same concept: environment variables in `~/.bashrc` / `~/.zshrc`.
|
||||
|
||||
| Codex CLI (`config.toml`) | Claude Code (env var) | Effect |
|
||||
|---------------------------|-----------------------|--------|
|
||||
| `model_max_output_tokens = 65536` | `CLAUDE_CODE_MAX_OUTPUT_TOKENS=65536` | Max tokens per response |
|
||||
| `model_context_window = 400000` | *(determined by the model — not configurable)* | Context window |
|
||||
| `tool_output_token_limit = 32768` | *(not directly exposed)* | Per-tool history cap |
|
||||
|
||||
```bash
|
||||
# ~/.bashrc — Claude Code token cap (equivalent to Codex model_max_output_tokens)
|
||||
export CLAUDE_CODE_MAX_OUTPUT_TOKENS=65536
|
||||
```
|
||||
|
||||
> **Why 64k and not 128k?** The Claude 4.x family supports up to 128k output, but for interactive coding sessions 64k covers any file or diff you realistically generate. Setting 128k reserves the full slot on every request, which increases latency and cost even for short responses. Use 128k only for batch/document-generation workflows where you routinely need very long outputs.
|
||||
|
||||
---
|
||||
|
||||
## About `[notice.model_migrations]`
|
||||
|
||||
Auto-generated by the Codex CLI to record acknowledged deprecation warnings. **Not an alias system** — safe to ignore.
|
||||
|
||||
---
|
||||
|
||||
## Quick reference — CLI flags
|
||||
|
||||
| Flag | Short | Effect |
|
||||
|------|-------|--------|
|
||||
| `--model <id>` | `-m` | Overrides `model` for the current invocation |
|
||||
| `--profile <name>` | `-p` | Loads `~/.codex/profile-<name>.config.toml` |
|
||||
| `--config key=value` | `-c` | Overrides any config.toml field |
|
||||
| `--enable <feature>` | — | Force-enables a feature flag |
|
||||
| `--disable <feature>` | — | Force-disables a feature flag |
|
||||
|
||||
Inside an interactive session:
|
||||
|
||||
| Command | Effect |
|
||||
|---------|--------|
|
||||
| `/model` | Opens the model picker |
|
||||
| `/help` | Lists all slash commands |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**`Error: model not found`**
|
||||
Verify the model exists in OmniRoute with the correct prefix. Open `/dashboard/providers/<provider>` and check available models.
|
||||
|
||||
**`Authentication error`**
|
||||
Confirm `OMNIROUTE_API_KEY` is exported: `echo $OMNIROUTE_API_KEY`.
|
||||
|
||||
**`Connection refused`**
|
||||
Verify OmniRoute is running and the `base_url` host/port is correct for your network (local vs Tailscale vs VPS).
|
||||
|
||||
**Session crashes near context limit**
|
||||
Set `model_context_window` and `model_auto_compact_token_limit` explicitly for the model you are using. See the context window table above.
|
||||
|
||||
**Compaction fires too late / history is cut**
|
||||
Lower `model_auto_compact_token_limit` to trigger compaction earlier (e.g. 75% of the window). Never set it above 90% — silently ignored.
|
||||
|
||||
**DeepSeek / Mistral returns 404**
|
||||
You are likely pointing Codex directly at the provider API. Route through OmniRoute — it translates Responses API → Chat Completions automatically. Confirm `base_url` points to your OmniRoute instance, not directly to `api.deepseek.com` or `api.mistral.ai`.
|
||||
@@ -87,6 +87,7 @@ import { isClaudeExtraUsageBlockEnabled } from "@/lib/providers/claudeExtraUsage
|
||||
import { parseExtraApiKeys } from "@/shared/utils/parseApiKeys";
|
||||
import { compareTr } from "@/shared/utils/turkishText";
|
||||
import RiskNoticeModal from "../components/RiskNoticeModal";
|
||||
import CodexCliGuideModal from "../components/CodexCliGuideModal";
|
||||
import { isRiskAcknowledged, useRiskAcknowledged } from "../hooks/useRiskAcknowledged";
|
||||
import { resolveDashboardProviderInfo } from "../providerPageUtils";
|
||||
import {
|
||||
@@ -1475,6 +1476,7 @@ export default function ProviderDetailPage() {
|
||||
);
|
||||
const [exportingCodexAuthId, setExportingCodexAuthId] = useState<string | null>(null);
|
||||
const [importCodexModalOpen, setImportCodexModalOpen] = useState(false);
|
||||
const [codexCliGuideOpen, setCodexCliGuideOpen] = useState(false);
|
||||
// "Adicionar Externo": public shareable device-flow link state.
|
||||
const [externalLinkModalOpen, setExternalLinkModalOpen] = useState(false);
|
||||
const [externalLinkUrl, setExternalLinkUrl] = useState("");
|
||||
@@ -4573,6 +4575,16 @@ export default function ProviderDetailPage() {
|
||||
Experimental OAuth
|
||||
</Button>
|
||||
)}
|
||||
{providerId === "codex" && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon="menu_book"
|
||||
onClick={() => setCodexCliGuideOpen(true)}
|
||||
>
|
||||
Codex CLI Guide
|
||||
</Button>
|
||||
)}
|
||||
{providerId === "codex" && (
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -5328,6 +5340,11 @@ export default function ProviderDetailPage() {
|
||||
isCcCompatible={isCcCompatible}
|
||||
/>
|
||||
)}
|
||||
{/* Codex CLI Guide Modal */}
|
||||
<CodexCliGuideModal
|
||||
isOpen={codexCliGuideOpen}
|
||||
onClose={() => setCodexCliGuideOpen(false)}
|
||||
/>
|
||||
{/* Codex Import Auth Modal */}
|
||||
{providerId === "codex" && importCodexModalOpen && (
|
||||
<ImportCodexAuthModal
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import ReactMarkdown, { type Components } from "react-markdown";
|
||||
import { Modal, Button } from "@/shared/components";
|
||||
|
||||
const markdownComponents: Components = {
|
||||
h1({ children }) {
|
||||
return <h1 className="mb-4 text-xl font-bold text-text-main">{children}</h1>;
|
||||
},
|
||||
h2({ children }) {
|
||||
return (
|
||||
<h2 className="mt-6 mb-3 flex items-center gap-2 text-base font-bold text-text-main first:mt-0">
|
||||
<span className="material-symbols-outlined text-[16px] text-primary">terminal</span>
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
},
|
||||
h3({ children }) {
|
||||
return <h3 className="mt-4 mb-2 text-sm font-semibold text-text-main/80">{children}</h3>;
|
||||
},
|
||||
h4({ children }) {
|
||||
return (
|
||||
<h4 className="mt-3 mb-1 text-xs font-semibold uppercase tracking-wide text-text-muted">
|
||||
{children}
|
||||
</h4>
|
||||
);
|
||||
},
|
||||
p({ children }) {
|
||||
return <p className="mb-2 text-sm leading-relaxed text-text-muted">{children}</p>;
|
||||
},
|
||||
ul({ children }) {
|
||||
return <ul className="my-2 flex flex-col gap-1">{children}</ul>;
|
||||
},
|
||||
ol({ children }) {
|
||||
return <ol className="my-2 flex flex-col gap-1 list-decimal list-inside">{children}</ol>;
|
||||
},
|
||||
li({ children }) {
|
||||
return (
|
||||
<li className="ml-2 flex items-start text-sm leading-relaxed text-text-muted">
|
||||
<span className="mr-2 mt-2 size-1 shrink-0 rounded-full bg-text-muted/40" />
|
||||
<span>{children}</span>
|
||||
</li>
|
||||
);
|
||||
},
|
||||
strong({ children }) {
|
||||
return <strong className="font-semibold text-text-main">{children}</strong>;
|
||||
},
|
||||
code({ children, className }) {
|
||||
const isBlock = className?.includes("language-");
|
||||
if (isBlock) {
|
||||
return (
|
||||
<code className="block w-full whitespace-pre-wrap font-mono text-[12px] text-text-main">
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<code className="rounded border border-black/5 bg-bg-subtle px-1 py-0.5 font-mono text-[12px] text-text-main dark:border-white/5">
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
pre({ children }) {
|
||||
return (
|
||||
<pre className="my-2 overflow-x-auto rounded-lg border border-border bg-bg-subtle p-3">
|
||||
{children}
|
||||
</pre>
|
||||
);
|
||||
},
|
||||
blockquote({ children }) {
|
||||
return (
|
||||
<blockquote className="my-2 border-l-2 border-primary/40 pl-3 text-sm text-text-muted/80 italic">
|
||||
{children}
|
||||
</blockquote>
|
||||
);
|
||||
},
|
||||
table({ children }) {
|
||||
return (
|
||||
<div className="my-3 overflow-x-auto rounded-lg border border-border">
|
||||
<table className="w-full text-sm">{children}</table>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
thead({ children }) {
|
||||
return <thead className="bg-bg-subtle text-xs text-text-muted">{children}</thead>;
|
||||
},
|
||||
tr({ children }) {
|
||||
return <tr className="border-b border-border last:border-0">{children}</tr>;
|
||||
},
|
||||
th({ children }) {
|
||||
return <th className="px-3 py-2 text-left font-semibold">{children}</th>;
|
||||
},
|
||||
td({ children }) {
|
||||
return <td className="px-3 py-2 text-text-muted">{children}</td>;
|
||||
},
|
||||
hr() {
|
||||
return <hr className="my-4 border-border" />;
|
||||
},
|
||||
a({ href, children }) {
|
||||
if (!href) return <span>{children}</span>;
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
interface CodexCliGuideModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function CodexCliGuideModal({ isOpen, onClose }: CodexCliGuideModalProps) {
|
||||
const [content, setContent] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen || content) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
|
||||
fetch("/api/docs/codex-cli")
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(`${res.status}`);
|
||||
return res.json() as Promise<{ content: string }>;
|
||||
})
|
||||
.then((data) => setContent(data.content))
|
||||
.catch(() => setError(true))
|
||||
.finally(() => setLoading(false));
|
||||
}, [isOpen, content]);
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} title="Codex CLI — Guia de Configuração" onClose={onClose}>
|
||||
<div className="max-h-[70vh] overflow-y-auto pr-1">
|
||||
{loading && (
|
||||
<div className="flex flex-col items-center justify-center py-16 gap-3">
|
||||
<span className="material-symbols-outlined animate-spin text-[28px] text-text-muted/50">
|
||||
sync
|
||||
</span>
|
||||
<p className="text-sm text-text-muted">Carregando guia…</p>
|
||||
</div>
|
||||
)}
|
||||
{error && (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-text-muted gap-3">
|
||||
<span className="material-symbols-outlined text-[40px] text-red-500/50">
|
||||
error_outline
|
||||
</span>
|
||||
<p className="text-sm">Não foi possível carregar o guia.</p>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setContent("");
|
||||
setError(false);
|
||||
}}
|
||||
>
|
||||
Tentar novamente
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{!loading && !error && content && (
|
||||
<div className="p-1">
|
||||
<ReactMarkdown components={markdownComponents}>{content}</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
13
src/app/api/docs/codex-cli/route.ts
Normal file
13
src/app/api/docs/codex-cli/route.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { readFile } from "fs/promises";
|
||||
import path from "path";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const filePath = path.join(process.cwd(), "docs/guides/CODEX-CLI-CONFIGURATION.md");
|
||||
const content = await readFile(filePath, "utf-8");
|
||||
return NextResponse.json({ content });
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Guide not found" }, { status: 404 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user