# llms-full (private-aware)
> Built from GitHub files and website pages. Large files may be truncated.

--- .cursor/commands/tm/install-taskmaster.md ---
Check if Task Master is installed and install it if needed.

This command helps you get Task Master set up globally on your system.

## Detection and Installation Process

1. **Check Current Installation**
   ```bash
   # Check if task-master command exists
   which task-master || echo "Task Master not found"

   # Check npm global packages
   npm list -g task-master-ai
   ```

2. **System Requirements Check**
   ```bash
   # Verify Node.js is installed
   node --version

   # Verify npm is installed
   npm --version

   # Check Node version (need 16+)
   ```

3. **Install Task Master Globally**
   If not installed, run:
   ```bash
   npm install -g task-master-ai
   ```

4. **Verify Installation**
   ```bash
   # Check version
   task-master --version

   # Verify command is available
   which task-master
   ```

5. **Initial Setup**
   ```bash
   # Initialize in current directory
   task-master init
   ```

6. **Configure AI Provider**
   Ensure you have at least one AI provider API key set:
   ```bash
   # Check current configuration
   task-master models --status

   # If no API keys found, guide setup
   echo "You'll need at least one API key:"
   echo "- ANTHROPIC_API_KEY for Claude"
   echo "- OPENAI_API_KEY for GPT models"
   echo "- PERPLEXITY_API_KEY for research"
   echo ""
   echo "Set them in your shell profile or .env file"
   ```

7. **Quick Test**
   ```bash
   # Create a test PRD
   echo "Build a simple hello world API" > test-prd.txt

   # Try parsing it
   task-master parse-prd test-prd.txt -n 3
   ```

## Troubleshooting

If installation fails:

**Permission Errors:**
```bash
# Try with sudo (macOS/Linux)
sudo npm install -g task-master-ai

# Or fix npm permissions
npm config set prefix ~/.npm-global
export PATH=~/.npm-global/bin:$PATH
```

**Network Issues:**
```bash
# Use different registry
npm install -g task-master-ai --registry https://registry.npmjs.org/
```

**Node Version Issues:**
```bash
# Install Node 20+ via nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install 20
nvm use 20
```

## Success Confirmation

Once installed, you should see:
```
✅ Task Master installed
✅ Command 'task-master' available globally
✅ AI provider configured
✅ Ready to use slash commands!

Try: /taskmaster:init your-prd.md
```

## Next Steps

After installation:
1. Run `/taskmaster:status` to verify setup
2. Configure AI providers with `/taskmaster:setup-models`
3. Start using Task Master commands!

--- .cursor/commands/tm/quick-install-taskmaster.md ---
Quick install Task Master globally if not already installed.

Execute this streamlined installation:

```bash
# Check and install in one command
task-master --version 2>/dev/null || npm install -g task-master-ai

# Verify installation
task-master --version

# Quick setup check
task-master models --status || echo "Note: You'll need to set up an AI provider API key"
```

If you see "command not found" after installation, you may need to:
1. Restart your terminal
2. Or add npm global bin to PATH: `export PATH=$(npm bin -g):$PATH`

Once installed, you can use all the Task Master commands!

Quick test: Run `/taskmaster:help` to see all available commands.

--- docs/improve-mcp-process.md ---
Title:

* Consolidate llms.txt ingestion into a single MCP “registry” server (gitmcp) with automated refresh

Summary:

* The current setup uses multiple MCP servers (`mcpdoc` and `mcp-remote`) per doc set, with redundant access methods and large configuration surface area.
* Implement a single “llms-txt registry” repository that stores all generated `*-llms*.txt` artifacts and expose it through one MCP server entry (via `mcp-remote` → `gitmcp.io`).
* Add an automated refresh pipeline that runs the Python `lmstxt <url>` generator (venv-aware) and publishes updated artifacts deterministically.

Background / Context:

* User goal: “host these in one place and just call one tool instead of having multiple” MCP servers.
* Current generation workflow is Python-based: (1) activate venv, (2) run `lmstxt <repo-url>`, (3) output goes to `artifacts/`.
* Proposed direction accepted by user: “Pattern A sounds good” (registry repo + gitmcp + one MCP server).

Current Behavior (Actual):

* Multiple MCP servers per doc set (e.g., lots of `uvx mcpdoc ... --urls ...`) and mixed access methods (local `mcpdoc` + remote `mcp-remote` for overlapping sources).
* Manual steps required to regenerate artifacts (venv activation + generator run), and outputs are not centralized for consumption.

Expected Behavior:

* A single, centralized repository containing all generated `*-llms*.txt` artifacts (e.g., under `docs/<source-id>/`).
* A single MCP server entry in each client config (Gemini CLI `settings.json`, Codex `config.toml`) pointing to the registry via `mcp-remote` + `gitmcp.io`.
* Automated refresh (scheduled + manual trigger) that regenerates artifacts and commits updates only when content changes.

Requirements:

* Create a “registry” repo (example name: `llms-txt-registry`) to store generated outputs in a stable structure (e.g., `docs/<id>/...`).
* Maintain a source manifest (e.g., `sources.json`) mapping stable `id` → `repo_url`.
* Implement a refresh script that:

  * Is venv-aware (creates/uses `.venv/` without manual activation).
  * Installs the Python generator via `requirements.txt`.
  * Runs the generator per repo URL.
  * Copies outputs from `artifacts/<owner>/<repo>/*-llms*.txt` into `docs/<id>/`.
  * Supports overriding output directory (`--output-dir` / `OUTPUT_DIR`) to avoid cross-source mixing.
* Add GitHub Actions workflow to refresh on a schedule and on manual dispatch, committing only if diffs exist.
* Update client configs to use one MCP server:

  * Gemini CLI: one `mcpServers.llms-registry` using `npx mcp-remote https://gitmcp.io/<user>/llms-txt-registry`.
  * Codex: one `[mcp_servers.llms-registry]` entry with the same remote endpoint.
* Operational guidance:

  * Prefer one access method per source (avoid duplicating same docs via both local `mcpdoc` and remote).
  * Keep registry limited to generated `*-llms*.txt` artifacts; keep “live docs” servers separate.

Out of Scope:

* Designing/implementing a custom “llms-hub” MCP server (local search/index server) beyond the registry + gitmcp approach (mentioned as an alternative).
* Deciding public vs private hosting approach (not decided; identified as a decision point).

Reproduction Steps:

* Not provided (this is an improvement/architecture change request rather than a single failing repro).

Environment:

* OS: Unknown
* Clients: Gemini CLI (`settings.json`) and Codex (`config.toml`) (per conversation).
* Generator: Python CLI invoked as `lmstxt <url>`; outputs to `artifacts/`.
* MCP tooling: `npx mcp-remote`, `uvx mcpdoc` (current), gitmcp endpoint.

Evidence:

* User workflow: “source activate the venv … `lmstxt <repo-url>` … `artifacts/` is the output directory.”
* Target architecture: “Expose that one place via one MCP server… Pattern A — ‘Registry repo + gitmcp’.”
* Config snippets for one-server approach (Gemini + Codex).
* Proposed refresh automation (GitHub Actions).

Decisions / Agreements:

* Adopt Pattern A (“Registry repo + gitmcp”) as the preferred direction. (per user: “Pattern A sounds good”).

Open Items / Unknowns:

* Registry visibility/hosting: public via gitmcp vs private/self-hosted endpoint.
* Exact installation method for the Python `lmstxt` generator in CI (`requirements.txt` contents / packaging source).
* Whether registry should store both `llms.txt` and `llms-full.txt` for all sources (implied but not strictly specified).

Risks / Dependencies:

* CI refresh depends on having a working runtime environment for the Python generator (venv + dependencies).
* If registry is private, gitmcp accessibility may be a blocker, requiring alternate hosting.
* Large sources (e.g., “TanStack”) previously drove “command-line explosion” in config; migration must ensure equivalent coverage in the registry.

Acceptance Criteria:

* [ ] A registry repo exists with `sources.json`, `docs/`, and refresh automation.
* [ ] Running the refresh script produces/updates `docs/<id>/` with generated `*-llms*.txt` copied from generator outputs under `artifacts/` (or overridden output dir).
* [ ] GitHub Actions refresh runs on schedule and commits only when diffs exist.
* [ ] Gemini CLI and Codex each use exactly one MCP server entry pointing at the registry via `mcp-remote`/gitmcp.
* [ ] Redundant per-source MCP servers (local `mcpdoc` and duplicated remote endpoints) can be removed once their artifacts are present in the registry.

Priority & Severity (if inferable from text):

* Priority: Not provided
* Severity: Not provided

Labels (optional):

* enhancement
* mcp
* automation
* devops
* docs-ingestion
* config-management


--- docs/prompts.md ---
00.planning/create-plan.md
```
1 | ---
2 | description: Create a comprehensive task plan with context, success criteria, approach, and risks
3 | argument-hint: TASK=<task-description-or-goal>
4 | ---
5 | 
6 | <task>
7 | $TASK
8 | </task>
9 | 
10 | # Create Plan (slash command)
11 | 
12 | Goal: draft a full task plan in `docs/tasks/todo/<XX>-<name>.md`.
13 | 
14 | Always do this first:
15 | - Read AGENTS.md and follow all rules/constraints.
16 | 
17 | Plan requirements (keep concise, but complete):
18 | - Context: 2–3 bullet recap of the problem/goal.
19 | - Success criteria / acceptance: bullet list with measurable checks.
20 | - Deliverables: code/docs/tests to produce.
21 | - Approach: ordered steps (no workarounds; sustainable, clean implementation).
22 | - Risks / unknowns: note dependencies, edge cases, perf/security concerns.
23 | - Testing & validation: what to run (unit/integration/e2e), data sets, platforms.
24 | - Rollback / escape hatches: brief note if applicable.
25 | - Owner / date: stamp with today’s date.
26 | 
27 | After writing the plan:
28 | - Save at path `docs/tasks/todo/<XX>-<name>.md`.
29 | - Ask the user: “What are the most important questions to confirm before implementation?” and list your top 3–5 clarifying questions.
30 | 
31 | Suggested prompt body:
32 | ```
33 | Context
34 | - ...
35 | - ...
36 | 
37 | Success criteria
38 | - ...
39 | 
40 | Deliverables
41 | - ...
42 | 
43 | Approach
44 | 1) ...
45 | 2) ...
46 | 3) ...
47 | 
48 | Risks / unknowns
49 | - ...
50 | 
51 | Testing & validation
52 | - ...
53 | 
54 | Rollback / escape hatch
55 | - ...
56 | 
57 | Owner/Date
58 | - <you> / <YYYY-MM-DD>
59 | ```
60 | 
61 | Reminder:
62 | - No workarounds or half measures; design for long-term maintainability.
63 | - If info is missing, include assumptions and surface them in the questions you ask the user at the end.
```

00.planning/plan-review.md
```
1 | <plan>
2 | $1
3 | </plan>
4 | 
5 | review the current <plan />
6 | 
7 | - reflects the current codebase (files, patterns, constraints)
8 | - no fallbacks, no feature flags
9 | - full change or full refactor only; no “future use” leftovers
10 | - list code smells and caveats
11 | - clear scope and out of scope
12 | - performance, security, and privacy impact
13 | - decision: if the analysis indicates >90% satisfaction with the implementation, mark GREEN LIGHT
14 | 
15 | 
```

00.planning/planning-process.md
```
1 | ---
2 | phase: 'P1 Plan & Scope'
3 | gate: 'Scope Gate'
4 | status: 'confirm problem, users, Done criteria, and stack risks are logged.'
5 | previous:
6 |   - 
7 | next:
8 |   -
9 |   -
10 | ---
11 | 
12 | # Planning Process
13 | 
14 | Trigger: /planning-process
15 | 
16 | Purpose: Draft, refine, and execute a feature plan with strict scope control and progress tracking.
17 | 
18 | ## Steps
19 | 
20 | 1. If no plan file exists, create `PLAN.md`. If it exists, load it.
21 | 2. Draft sections: **Goal**, **User Story**, **Milestones**, **Tasks**, **Won't do**, **Ideas for later**, **Validation**, **Risks**.
22 | 3. Trim bloat. Convert vague bullets into testable tasks with acceptance criteria.
23 | 4. Tag each task with an owner and estimate. Link to files or paths that will change.
24 | 5. Maintain two backlogs: **Won't do** (explicit non-goals) and **Ideas for later** (deferrable work).
25 | 6. Mark tasks done after tests pass. Append commit SHAs next to completed items.
26 | 7. After each milestone: run tests, update **Validation**, then commit `PLAN.md`.
27 | 
28 | ## Output format
29 | 
30 | - Update or create `PLAN.md` with the sections above.
31 | - Include a checklist for **Tasks**. Keep lines under 100 chars.
32 | 
33 | ## Examples
34 | 
35 | **Input**: "Add OAuth login"
36 | 
37 | **Output**:
38 | 
39 | - Goal: Let users sign in with Google.
40 | - Tasks: [ ] add Google client, [ ] callback route, [ ] session, [ ] E2E test.
41 | - Won't do: org SSO.
42 | - Ideas for later: Apple login.
43 | 
44 | ## Notes
45 | 
46 | - Planning only. No code edits.
47 | - Assume a Git repo with test runner available.
```

00.planning/prd-generator.md
```
1 | <!-- path: prd-generator.md -->
2 | 
3 | <!--
4 | $1 = source document path or raw Markdown to mine (single input)
5 | $2 = product name override (optional)
6 | $3 = maximum links to mine in $1 (default 20)
7 | $4 = maximum features (default 8)
8 | -->
9 | 
10 | # PRD Generator Template
11 | 
12 | **Goal**
13 | From $1, generate a dependency-aware PRD using RPG method. Infer everything possible from $1. Only use $2–$4 to cap or override.
14 | 
15 | **Output file**
16 | Write plain text to `prd.txt`. Use **exactly** these sections in order, each separated by **one** blank line:
17 | 
18 | # Overview
19 | # Core Features
20 | # User Experience
21 | # Technical Architecture
22 | # Development Roadmap
23 | # Logical Dependency Chain
24 | # Risks and Mitigations
25 | # Appendix
26 | 
27 | ---
28 | 
29 | ## Scavenge and Normalize (do before writing)
30 | 
31 | 1) **Parse $1**
32 | 
33 | - Accept a filesystem path or raw Markdown.
34 | - Extract: product name, problem, users, constraints, notable links, code paths, APIs, data models, UI hints, prior art.
35 | - If a field is missing, infer conservatively from context; if uncertain, mark with `{TBD:<label>}`.
36 | 
37 | 2) **Canonicalize product name**
38 | 
39 | - Prefer $2 if present, else best inferred name.
40 | 
41 | 3) **RPG framing** *(WHAT vs HOW)*
42 | 
43 | - WHAT = capabilities and features.
44 | - HOW = modules, files, APIs, data, infra.
45 | - Map WHAT→HOW later in “Logical Dependency Chain”.
46 | - Keep features atomic and independently testable.
47 | 
48 | 4) **Limit scope**
49 | 
50 | - Cap features to $4 (default 8).
51 | - Cap link-derived evidence to $3 (default 20).
52 | - Prioritize content that supports MVP.
53 | 
54 | ---
55 | 
56 | ## Section Specifications
57 | 
58 | ### # Overview
59 | 
60 | - Problem statement, target users, value. Keep implementation out. Populate from $1.
61 | 
62 | ### # Core Features
63 | List each feature with:
64 | 
65 | - **What**: one sentence.
66 | - **Why**: user value or constraint tie-in.
67 | - **High-level How**: approach without code.
68 | - **BDD**:
69 |   - Given …
70 |   - When …
71 |   - Then …
72 | 
73 | ### # User Experience
74 | 
75 | - Personas, key user flows, UX and accessibility notes. Include the fastest path to “first successful outcome.”
76 | 
77 | ### # Technical Architecture
78 | 
79 | - System components, data models, APIs/integrations, infrastructure, and NFRs. Defer deep design debates to Appendix.
80 | 
81 | ### # Development Roadmap
82 | 
83 | - **MVP**: smallest end-to-end slice with acceptance criteria.
84 | - **Future Enhancements**: clearly separated.
85 | - No dates. Only scope and testable outcomes.
86 | 
87 | ### # Logical Dependency Chain
88 | Use RPG style. Provide a topologically sorted list from foundations to layers. For each module:
89 | 
90 | - **Module**: name
91 | - **Maps to capability**: <capability>
92 | - **Depends on**: [modules]
93 | - **Delivers**: observable behavior
94 | 
95 | Ensure foundation modules have **no** dependencies and no cycles exist.
96 | 
97 | ### # Risks and Mitigations
98 | For each risk:
99 | 
100 | - **Description**
101 | - **Likelihood**: High/Medium/Low
102 | - **Impact**: High/Medium/Low
103 | - **Mitigation**
104 | - **Fallback**
105 | 
106 | ### # Appendix
107 | 
108 | - **Assumptions**: bullets.
109 | - **Research findings**: distilled from $1. Quote minimally.
110 | - **Context notes**: `- <visible text> — inferred topic`
111 | - **Technical specs**: any schemas, API shapes, or file paths referenced.
112 | 
113 | ---
114 | 
115 | ## Validation Checks (reject and fix before writing)
116 | 
117 | - All headers present and in exact order with single blank lines between sections.
118 | - Each feature has full BDD triad.
119 | - Roadmap has MVP and Enhancements, each with acceptance criteria.
120 | - Dependency chain is acyclic, foundations first, maps WHAT→HOW.
121 | - Risks include likelihood and impact.
122 | - No secrets or live URLs leaked; convert links in $1 to **visible text only** in Appendix context notes.
123 | 
124 | ---
125 | 
126 | ## Writing Rules
127 | 
128 | - Plain text only. No markdown tables. No extra sections.
129 | - Prefer concise sentences. No dates. No promises. Only testable statements.
130 | - Use `{TBD:<label>}` for unknowns rather than guessing.
131 | 
132 | ---
133 | 
134 | ## Example Skeleton (format only)
135 | 
136 | # Overview
137 | <problem, users, value>
138 | 
139 | # Core Features
140 | 
141 | - Feature: <name>
142 |   - What: …
143 |   - Why: …
144 |   - High-level How: …
145 |   - BDD:
146 |     - Given …
147 |     - When …
148 |     - Then …
149 | 
150 | # User Experience
151 | <personas, key flows, a11y>
152 | 
153 | # Technical Architecture
154 | <components, data, APIs, infra, NFRs>
155 | 
156 | # Development Roadmap
157 | ## MVP
158 | 
159 | - Outcome: …
160 | - Acceptance criteria: …
161 | 
162 | ## Future Enhancements
163 | 
164 | - Item: …
165 | - Acceptance criteria: …
166 | 
167 | # Logical Dependency Chain
168 | 
169 | - Module: <foundation-a>
170 |   - Maps to capability: …
171 |   - Depends on: []
172 |   - Delivers: …
173 | - Module: <layer-1-x>
174 |   - Maps to capability: …
175 |   - Depends on: [foundation-a]
176 |   - Delivers: …
177 | 
178 | # Risks and Mitigations
179 | 
180 | - Description: …
181 |   - Likelihood: …
182 |   - Impact: …
183 |   - Mitigation: …
184 |   - Fallback: …
185 | 
186 | # Appendix
187 | 
188 | - Assumptions:
189 |   - …
190 | - Research findings:
191 |   - …
192 | - Context notes:
193 |   - <visible text> — <inferred topic>
194 | - Technical specs:
195 |   - …
196 | 
197 | ```
```

01.prework/need-docs-v3.md
```
1 | <!--
2 | $1 = Project scope / feature
3 | $2 = Component / module impacted
4 | $3 = Target date or milestone
5 | $4 = Repos / PRs / specs to reference
6 | $5 = Document owners / SMEs
7 | $6 = Known risks or constraints
8 | $7 = Definition of Done / acceptance criteria
9 | -->
10 | 
11 | # {$2 or Document Gap Assessment}
12 | 
13 | ## Context
14 | 
15 | - Scope/feature: **$1**
16 | - Impacted area: **$2**
17 | - Target date: **$3**
18 | - References: **$4**
19 | - Owners/SMEs: **$5**
20 | - Risks/constraints: **$6**
21 | - Definition of Done: **$7**
22 | 
23 | ## Task
24 | 
25 | Determine whether additional documentation is required to complete the implementation correctly and confidently.
26 | 
27 | ## Expected Deliverable
28 | 
29 | Provide a table of needed documents followed by a single-line verdict.
30 | 
31 | ### Documents Needed (table)
32 | 
33 | - **document_name**
34 | - **why_needed**
35 | - **key_contents_expected**
36 | - **current_status** (available / missing / draft)
37 | - **source_or_owner**
38 | - **blocking_risk** (low / medium / high)
39 | 
40 | **Verdict:** “Ready” _or_ “Not ready—waiting on {items}.”
41 | 
42 | ## Analysis Aids (fill as applicable)
43 | 
44 | ### Affected files / areas
45 | 
46 | _List the files, packages, services, or endpoints that depend on the missing info._
47 | 
48 | ### Root cause of gaps
49 | 
50 | _Explain why the information isn’t available or is ambiguous._
51 | 
52 | ### Proposed fix
53 | 
54 | _Describe how to obtain or author the missing docs (owner, format, timeline)._
55 | 
56 | ### Tests impacted
57 | 
58 | _Call out test plans or cases that are blocked by missing details._
59 | 
60 | ### Docs gaps
61 | 
62 | _Summarize the precise unanswered questions and assumptions._
63 | 
64 | ### Open questions
65 | 
66 | _Bullet the questions that must be answered before implementation proceeds._
67 | 
68 | ## Output format
69 | 
70 | Return:
71 | 
72 | 1. A Markdown table with the specified columns (one row per document).
73 | 2. A single line beginning with **Verdict:** as specified above.
```

01.prework/need-docs.md
```
1 | Okay, do you need any missing documents to implement this correctly?
```

01.prework/prompt-sequence-generator.md
```
1 | # Prompt: Generate Prompt Execution Sequence
2 | 
3 | **Purpose:** Given a high-level goal and a set of available prompts, generate the logical execution sequence required to accomplish that goal by chaining the prompts together.
4 | 
5 | ---
6 | 
7 | ### **Inputs**
8 | 
9 | - **High-Level Goal:** {{high_level_goal}}
10 |   - _A clear, one-sentence description of the final outcome the user wants to achieve._
11 |   - _Example: "Create and document a pull request for the currently staged changes."_
12 | 
13 | - **Available Prompts:**
14 | 
15 |   ```
16 |   {{available_prompts}}
17 |   ```
18 | 
19 |   - _A list of candidate prompt names (e.g., from the output of `rank-root-prompts`)._
20 |   - _Example: ['pr-desc.md', 'commit-msg.md', 'changed-files.md', 'review.md', 'release-notes.md']_
21 | 
22 | - **Context (Optional):** {{context}}
23 |   - _Any additional context, such as the current state of the git repository or specific files of interest._
24 |   - _Example: "The user has already staged files using `git add`."_
25 | 
26 | ---
27 | 
28 | ### **Instructions for the AI**
29 | 
30 | 1. **Analyze the Goal:** Deconstruct the `{{high_level_goal}}` into a series of logical steps required to get from the starting state to the final outcome.
31 | 
32 | 2. **Map Prompts to Steps:** For each logical step, identify the most suitable prompt from the `{{available_prompts}}` list that can perform that step.
33 |     - Consider the inputs and outputs of each prompt to determine dependencies. A prompt's input is often the output of a previous one.
34 | 
35 | 3. **Establish Order:** Arrange the selected prompts into a numbered sequence based on their dependencies. The sequence should represent a complete and logical workflow.
36 | 
37 | 4. **Identify Gaps:** If any necessary step in the workflow cannot be fulfilled by one of the available prompts, explicitly state what action or prompt is missing.
38 | 
39 | ---
40 | 
41 | ### **Required Output Format**
42 | 
43 | **Execution Sequence:**
44 | 
45 | 1. **`[prompt_name_1.md]`**: [Brief justification for why this prompt is first and what it accomplishes.]
46 | 2. **`[prompt_name_2.md]`**: [Brief justification for why this prompt is second, and how it uses the output of the previous step.]
47 | 3. ...
48 | 
49 | **Identified Gaps (if any):**
50 | 
51 | - [Description of a missing step or prompt needed to complete the workflow.]
```

01.prework/rank-root-prompts.md
```
1 | <!--
2 | $1 = command name/identifier
3 | $2 = example user question
4 | $3 = project CWD path to scan for context (defaults to current directory)
5 | $4 = prompt directory path (defaults to "~/.codex/prompts")
6 | $5 = minimum relevance threshold (0–1)
7 | -->
8 | 
9 | # {Context-Aware Prompt Ranking Command}
10 | 
11 | ```md
12 | # Command: $1
13 | 
14 | # Usage: $1 "$2" "$3" "$4" "$5"
15 | 
16 | # Args:
17 | 
18 | # - {{query}}: $2
19 | 
20 | # - {{project_path}}: $3
21 | 
22 | # - {{prompt_path}}: $4
23 | 
24 | # - {{threshold}}: $5
25 | 
26 | prompt = """
27 | Task:
28 | Given a user inquiry ({{query}}) and the context of a software project located at {{project_path}}, your goal is to identify the most relevant prompt-definition file from the directory {{prompt_path}}.
29 | 
30 | Defaults:
31 | 
32 | - If {{project_path}} is missing or blank, use the current working directory.
33 | - If {{prompt_path}} is missing or blank, use "~/.codex/prompts".
34 | 
35 | Do the following:
36 | 
37 | 1. **Analyze Project Context**: Recursively scan {{project_path}} to understand its structure, languages, and purpose. Create a concise summary of the project context.
38 | 2. **Scan Prompts**: List all candidate prompt files in {{prompt_path}} (non-recursively).
39 | 3. **Evaluate Prompts**: For each candidate prompt file:
40 |    a) Read its content.
41 |    b) Create a one-sentence summary of its purpose and domain.
42 |    c) Compute a relevance score from 0 to 1. This score must measure how well the prompt's purpose aligns with the user's {{query}}, considering the project context summary. A higher score means the prompt is a better fit for solving the query within the given project.
43 | 4. **Rank and Filter**: Order the prompts by their relevance score in descending order.
44 | 5. **Generate Output**: Emit a compact markdown table with the columns: `filename | description | match_score` (rounded to 2 decimals).
45 | 
46 | Rules:
47 | 
48 | - The description must be 1–2 sentences capturing the prompt's purpose and domain.
49 | - Only include prompts in the table where `match_score` is greater than or equal to {{threshold}}.
50 | - If no prompts meet the threshold, output a single line: "No prompt exceeds threshold {{threshold}} — recommend creating a new prompt."
51 | 
52 | Acceptance:
53 | 
54 | - If one or more matches meet the {{threshold}}, a markdown table sorted by descending `match_score` is produced.
55 | - Otherwise, the single-line fallback message is produced.
56 | 
57 | !{echo "Scanning project: ${PROJECT_PATH_ARG:-.}"}
58 | !{echo "Searching for prompts in: ${PROMPT_PATH_ARG:-~/.codex/prompts}"}
59 | """
60 | ```
61 | 
62 | ## Output format
63 | 
64 | - **Preferred**: a markdown table with columns `filename | description | match_score` sorted by `match_score` (desc) and filtered by `{{threshold}}`.
65 | - **Fallback**: the exact one-line message when no entries meet `{{threshold}}`.
```

01.prework/research-better-lib.md
```
1 | ---
2 | description: Find a modern, faster JavaScript/TypeScript library alternative to a baseline library
3 | argument-hint: BASELINE_LIB=<library> DOMAIN_USE_CASE=<use-case> CANDIDATE_LIBS=<lib1,lib2,...> [SCALE=<size>] [TOP_N=<number>]
4 | ---
5 | 
6 | ## Problem statement
7 | - Goal: Find a modern, faster JavaScript/TypeScript library for $DOMAIN_USE_CASE that outperforms $BASELINE_LIB in latency and bundle size while maintaining or improving relevance/quality.
8 | - Context: Runs in Node 18+/browser, ESM‑first, TS types, no native deps; list size $SCALE; return $TOP_N suggestions per query.
9 | - Note: If SCALE or TOP_N are not provided, defaults to "5k–50k items" and "top-3" respectively.
10 | 
11 | ## Success metrics (make these explicit)
12 | - P95 latency/query: target <1 ms at 10k items; <5 ms at 50k items (Node laptop). Adjust as needed.
13 | - Bundle size: <25 KB min+gzip for core (no heavy optional modules).
14 | - Quality: NDCG@5 (or task‑specific metric) ≥ $BASELINE_LIB on the same corpus (≥1.0x).
15 | - Features: multi‑field weights, typo tolerance, diacritics, highlight ranges, incremental updates.
16 | - DX: ESM, TS types, active maintenance (<6 months since last release), permissive license.
17 | 
18 | ## Scope and exclusions
19 | - In‑scope: in‑memory, client/Node libraries (no servers, no external indexes).
20 | - Out‑of‑scope: hosted/search servers unless used only for comparison; heavy NLP stacks unless used for query expansion (phase 2).
21 | 
22 | ## Concrete research question to post/search
23 | - "Which modern JS/TS libraries outperform $BASELINE_LIB for $DOMAIN_USE_CASE at $SCALE, with ESM, TS types, and <25 KB gzip bundle? Compare $CANDIDATE_LIBS by p95 latency, relevance (NDCG@5 or equivalent), bundle size, multi‑field weighting, and maintenance."
24 | 
25 | ## Search queries (copy/paste)
26 | - benchmark "$BASELINE_LIB" vs $CANDIDATE_LIBS js performance
27 | - "$CANDIDATE_LIBS vs $BASELINE_LIB" latency relevance "typescript" "esm"
28 | - "$CANDIDATE_LIBS" library benchmark $DOMAIN_USE_CASE
29 | - $CANDIDATE_LIBS benchmark bundle size "diacritics" "highlight"
30 | - $CANDIDATE_LIBS javascript benchmark "multi field"
31 | - $CANDIDATE_LIBS js benchmark in‑memory search
32 | 
33 | ## Shortlist to evaluate
34 | Evaluate the following candidate libraries: $CANDIDATE_LIBS
35 | 
36 | (Example for fuzzy name/description search: fuzzysort, quick‑score, FlexSearch, MiniSearch, Orama/Lyra, fast‑fuzzy, match‑sorter)
37 | 
38 | ## Minimal benchmark design
39 | - Dataset: 25k items with fields relevant to the task (e.g., name, description). Combine synthetic + real.
40 | - Queries: 100 mixed queries (typos, prefixes, mid‑word, camelCase splits, etc.).
41 | - Procedure: warm index; run 1k queries; record mean/p95; compute NDCG@5 (or task metric) vs hand‑labeled relevancy; measure min+gzip bundle.
42 | - Environments: Node 20, Chrome stable. Configs: defaults + one tuned weights (e.g., name:0.7, description:0.3).
43 | 
44 | ## Decision rubric
45 | - Must meet latency + bundle targets and maintain ≥ $BASELINE_LIB quality metric.
46 | - Prefer simple multi‑field API and highlight support.
47 | - Tie‑breaker: maintenance cadence, type safety, ESM‑first.
48 | 
49 | ## Deliverables
50 | - One‑page results table (latency, quality metric, size, features).
51 | - Example integration snippet for our "did‑you‑mean"/ranking path.
52 | - Recommendation + migration notes (config, weights).
53 | 
54 | ---
55 | 
56 | ## Example usage
57 | 
58 | Usage: `/prompts:research-better-lib BASELINE_LIB=Fuse.js DOMAIN_USE_CASE="fuzzy matching tool names + short descriptions (10–500 chars)" CANDIDATE_LIBS="fuzzysort, quick-score, FlexSearch, MiniSearch, Orama" SCALE="5k–50k items" TOP_N="top-3"`
59 | 
60 | **Resulting research question:**
61 | "Which modern JS/TS fuzzy search libraries outperform Fuse.js on 5k–50k items for fuzzy matching tool names + short descriptions (10–500 chars), with ESM, TS types, and <25 KB gzip bundle? Please compare fuzzysort, quick‑score, FlexSearch, MiniSearch, Orama by p95 latency, relevance (NDCG@5), bundle size, multi‑field weighting, and maintenance."
62 | 
```

02.postwork/code-review-high.md
```
1 | ---
2 | description: Comprehensive high-level code review focusing on correctness, security, performance, and integration
3 | argument-hint: TASK=<change-description>
4 | ---
5 | 
6 | <task>
7 | $TASK
8 | </task>
9 | 
10 | <role>
11 |   You are a senior software engineer, security reviewer, and performance specialist. Review the provided change with a focus on correctness, security, performance, integration, test quality, and long-term maintainability. Be precise, cite file paths and line ranges, and prioritize risks that could impact users, data, uptime, or developer velocity.
12 | </role>
13 | 
14 | <objectives>
15 | - Identify correctness defects, code smells, and anti-patterns.
16 | - Surface exploitable security issues and data-protection risks.
17 | - Spot performance/regression risks and complexity hotspots.
18 | - Check integration points (APIs, events, DB, configs, CI/CD, infra) for compatibility and rollout safety.
19 | - Assess tests for sufficiency, signal, reliability, and coverage.
20 | - Recommend minimal, safe, high-leverage improvements and monitoring.
21 | </objectives>
22 | 
23 | <severity_rubric>
24 | - BLOCKER: Exploitable security flaw, data loss risk, broken build/deploy, user-impacting crash, irreversible migration risk, leaked secrets.
25 | - HIGH: Likely prod incident or major regression; authz/auth gaps; significant perf degradation; schema incompatibility.
26 | - MEDIUM: Correctness edge cases; non-exploitable but risky pattern; moderate perf concerns; flaky tests.
27 | - LOW: Maintainability, readability, style, minor test gaps; suggestions.
28 | - NIT: Optional polish.
29 | </severity_rubric>
30 | 
31 | <tasks>
32 | - Scope & Impact: Map all affected files/modules and why each is implicated. Note transitive and runtime impact (build, deploy, config, data).
33 | - Root Cause / Risk Analysis: Explain the change intent, risks introduced, and any hidden assumptions or environmental dependencies.
34 | - Security Review: Use the checklist below; escalate any secret exposure, injection, auth/authz flaws, SSRF/XXE/path traversal, insecure deserialization, command execution, mass assignment, CSRF/XSS, prototype pollution, weak crypto, missing TLS verification, permissive CORS, logging of secrets/PII, dependency vulns, or IaC/container misconfigurations.
35 | - Performance Review: Identify complexity issues, N+1 queries, unbounded loops, memory churn/leaks, blocking I/O on hot paths, missing indexes, cache misuse, chatty network calls, unnecessary allocations/boxing, and concurrency contention.
36 | - Integration Review: Validate API schema changes, versioning, backward/forward compatibility, idempotency, retries/timeouts/circuit breakers, feature-flag rollout, DB migrations (order/locking/rollback), message/event contracts, and config drift.
37 | - Testing Review: Evaluate unit/integration/e2e tests, coverage, negative/property-based cases, concurrency/time-dependent tests, fixture health, determinism, and flakiness risk. Propose a targeted test plan.
38 | - Observability & Operations: Check log levels, PII in logs, correlation/trace IDs, metrics and alerts, runbooks. Recommend what to monitor post-merge.
39 | - Documentation & DX: Flag missing or outdated README/CHANGELOG/ADRs/API docs/config comments/schema diagrams. Note onboarding and maintenance friction.
40 | - Minimal, Safe Fix: Propose the smallest viable change to eliminate blockers/high risks. Include tests and rollout/rollback steps.
41 | </tasks>
42 | 
43 | <detailed_checklist>
44 |   <category name="Correctness & Code Smells">
45 | - Duplicate code / long methods / large classes / deep nesting.
46 | - Leaky abstractions, tight coupling, poor cohesion, improper layering.
47 | - Dead code, unused variables/imports, TODOs that should be addressed now.
48 | - Non-idempotent operations where idempotency is required.
49 | - Edge cases: null/empty/NaN/overflow/encoding/timezone/locale.
50 | - Concurrency: shared state, race conditions, improper locking, async misuse.
51 |   </category>
52 | 
53 |   <category name="Security">
54 | - Secrets in code/logs/env/examples; credential handling, key rotation, KMS/secret manager usage.
55 | - Input validation & output encoding; SQL/NoSQL/LDAP/OS injection; XSS (reflected/stored/DOM); CSRF.
56 | - AuthN/AuthZ: broken access control, least privilege, multi-tenant boundaries, insecure direct object references.
57 | - SSRF/XXE/path traversal/file upload validation; sandboxing for untrusted inputs.
58 | - Crypto: algorithms, modes, IVs, nonces, randomness, key sizes, cert pinning/TLS verification.
59 | - CORS/security headers (CSP/HSTS/X-Frame-Options/SameSite), cookie flags.
60 | - Dependency & supply chain: pinned versions, known CVEs, pre/post-install scripts, integrity checks.
61 | - IaC/Containers: public buckets, open security groups (0.0.0.0/0), missing encryption, root containers, mutable latest tags.
62 | - Data protection & privacy: PII/PHI handling, minimization, retention, encryption at rest/in transit.
63 |   </category>
64 | 
65 |   <category name="Performance">
66 | - Time/space complexity, hot-path allocations, unnecessary synchronization.
67 | - N+1 queries, missing DB indexes, inefficient joins, full scans, pagination vs. streaming.
68 | - Caching: invalidation, eviction, key design, stampedes.
69 | - Network patterns: chattiness, batching, compression, timeouts, backoff.
70 | - Client-side perf (if UI): bundle size/regressions, critical path, images/fonts.
71 |   </category>
72 | 
73 |   <category name="Integration & Rollout Safety">
74 | - Backward/forward compatibility; versioned contracts; consumer-producer alignment.
75 | - DB migrations: zero-downtime (expand/migrate/contract), locks, data backfills, rollback plan.
76 | - Feature flags: default off, kill switch, gradual rollout, owner/expiry.
77 | - Resilience: retries with jitter, timeouts, circuit breakers, idempotency keys.
78 | - Config changes: validation, defaults, environment parity, secrets not in plain text.
79 | - CI/CD: reproducibility, cache safety, test gates, artifact signing.
80 |   </category>
81 | 
82 |   <category name="Testing & Quality Signals">
83 | - Tests exist for new behavior and regressions; meaningful assertions.
84 | - Coverage on critical branches/edge cases; mutation score (if available).
85 | - Isolation: minimal mocking vs. over-mocking; flaky patterns (sleep-based timing, order reliance).
86 | - Property-based/fuzz tests for parsers/validators/serializers.
87 | - Load/soak tests where perf risk exists; snapshot tests stability (if UI).
88 |   </category>
89 | 
90 |   <category name="Docs, Observability, Accessibility, i18n">
91 | - README/CHANGELOG/ADR/API docs updated; code comments for non-obvious logic.
92 | - Logs/metrics/traces with actionable context; PII redaction; alert thresholds.
93 | - Accessibility (if UI): semantics, focus order, labels, contrast, keyboard nav, ARIA use.
94 | - i18n/l10n: hard-coded strings, pluralization, date/time/number formats.
95 |   </category>
96 | </detailed_checklist>
97 | 
98 |   <output_requirements>
99 |     <instructions>
100 | - Produce a concise but comprehensive report.
101 | - Group findings by category and severity.
102 | - Reference exact file paths and line ranges (e.g., src/foo/bar.py:120–147).
103 | - Include brief code excerpts only as necessary (≤20 lines per finding).
104 | - Prefer specific, minimal fixes and tests that maximize risk reduction.
105 | - If information is missing, state the assumption and its impact.
106 |     </instructions>
107 |   </output_requirements>
108 | 
109 |   <report_skeleton>
110 | - Summary:
111 |   - What changed: <concise overview>
112 |   - Top risks: <1-3 bullets>
113 |   - Approval: <approve|comment|request_changes|blocker>
114 | 
115 | - Affected files:
116 |   - <path> — <reason> (<added|modified|deleted>)
117 | 
118 | - Root cause & assumptions:
119 |   - <analysis>
120 |   - Assumptions: <items>
121 | 
122 | - Findings (repeat per finding):
123 |   - [<severity>] [<category>] <short title>
124 |     - Where: <file:line-range>
125 |     - Evidence: <brief snippet_trace>
126 |     - Impact: <what breaks_who is affected>
127 |     - Standards: <CWE/OWASP/Policy refs>
128 |     - Repro: <steps>
129 |     - Recommendation: <minimal fix>
130 |     - Tests: <tests to add_update>
131 | 
132 | - Performance:
133 |   - Hotspots: <items>
134 |   - Complexity notes: <items>
135 |   - Bench/Monitoring plan: <how to measure & watch>
136 | 
137 | - Integration:
138 |   - API/contracts: <compat/versioning/idempotency>
139 |   - DB migrations: <expand-migrate-contract, locks, rollback>
140 |   - Feature flags & rollout: <plan/kill switch_owner>
141 |   - Resilience: <timeouts/retries/circuits>
142 |   - Rollback plan: <how to revert safely>
143 | 
144 | - Testing:
145 |   - Coverage: <statements_branches_critical_paths>
146 |   - Gaps: <cases>
147 |   - Flakiness risks: <items>
148 |   - Targeted test plan: <Given_When_Then bullets>
149 | 
150 | - Docs & Observability:
151 |   - Docs to update/create: <paths/sections>
152 |   - Logs/Metrics/Traces/Alerts: <plan>
153 |   - Runbook: <updates>
154 | 
155 | - Open questions:
156 |   - <items>
157 | 
158 | - Final recommendation:
159 |   - Decision: <approve|comment|request_changes|blocker>
160 |   - Must-fix before merge: <items>
161 |   - Nice-to-have post-merge: <items>
162 |   - Confidence: <low|medium|high>
163 |   </report_skeleton>
164 | 
165 | <process_notes>
166 | - Prioritize BLOCKER/HIGH issues. If any are found, set approval to “blocker” or “request_changes”.
167 | - Favor minimal, safe changes and targeted tests over broad refactors (unless safety demands it).
168 | - If diff is very large, focus on high-risk/new code paths, public interfaces, security-critical modules, and hot paths.
169 | - Reference concrete files/lines. Keep code excerpts minimal (≤20 lines). Do not rewrite large code blocks.
170 | - If required inputs are missing (e.g., DB migration script or API schema), flag as a risk and propose what is needed.
171 | </process_notes>
172 | 
173 | <constraints>
174 | - DO NOT write or generate full code implementations in this review. Provide patch outlines, pseudocode, or stepwise instructions only.
175 | - Maintain confidentiality: if a secret or sensitive data appears, describe it without reproducing it verbatim.
176 | </constraints>
177 | 
178 | <success_criteria>
179 | - Findings are specific, actionable, and ordered by severity and blast radius.
180 | - Every high-risk change has a minimal fix and a concrete test/monitoring plan.
181 | - Output follows the provided report skeleton (markdown text only).
182 | </success_criteria>
183 | 
184 | <reminder>DON'T CODE YET.</reminder>
```

02.postwork/code-review-low.md
```
1 | ---
2 | description: Lightweight code review focusing on code smells, security, performance, and test coverage
3 | argument-hint: TASK=<change-description>
4 | ---
5 | 
6 | <task>
7 | $TASK
8 | </task>
9 | 
10 | ## Role
11 | Senior engineer reviewing **only**: code smells, security, performance, and whether new tests are needed for the new feature.
12 | 
13 | ## Inputs
14 | - {CHANGE_SUMMARY}
15 | - {DIFF} + {FILES}
16 | - {CI_LOGS} {COVERAGE_SUMMARY} (optional)
17 | - {ENVIRONMENT} {API_SCHEMAS} {DB_MIGRATIONS} {DEPENDENCIES} (if relevant)
18 | 
19 | ## What to check
20 | ### Code Smells
21 | - Duplicates, long methods, deep nesting, dead code, unused imports
22 | - Leaky abstractions, tight coupling, improper layering
23 | - Edge cases: null, empty, timezones, encodings
24 | - Concurrency misuse and non-idempotent ops where required
25 | 
26 | ### Security
27 | - Secrets in code/logs; proper secret management
28 | - Input validation and output encoding; SQL/NoSQL/OS injection; XSS/CSRF
29 | - AuthN/AuthZ and multi-tenant boundaries
30 | - SSRF/XXE/path traversal/file upload validation
31 | - Crypto choices; TLS verification; CORS and security headers
32 | - Dependency CVEs and supply-chain risks; IaC/container misconfig
33 | 
34 | ### Performance
35 | - Time/space complexity; hot-path allocations; blocking I/O
36 | - N+1 queries; missing indexes; inefficient joins; full scans
37 | - Caching correctness and stampedes
38 | - Chatty network calls; batching; timeouts; backoff
39 | - Client bundle size and critical path (if UI)
40 | 
41 | ### Tests needed
42 | - Does new behavior have unit/integration/e2e tests
43 | - Edge cases, negative cases, concurrency/time-based cases
44 | - Minimal test plan to guard the change
45 | 
46 | ## Output format
47 | - **Summary**: what changed, top 1–3 risks, **Decision**: approve | request_changes | blocker
48 | - **Findings** grouped by **Smell | Security | Performance | Tests**
49 |   - `[severity] <title>`  
50 |     - Where: `<file:line-range>`  
51 |     - Impact: `<who/what is affected>`  
52 |     - Recommendation: `<smallest safe fix>`  
53 |     - Tests: `<tests to add/update>`
54 | - Cite exact files and line ranges. Keep code excerpts ≤20 lines.
55 | 
56 | ## Constraints
57 | - No full implementations. Pseudocode or patch outline only.
58 | - If data is missing, state the assumption and risk.
```

02.postwork/generate-tests.md
```
1 | ---
2 | phase: 'P5 Quality Gates & Tests'
3 | gate: 'Test Gate'
4 | status: 'targeted unit tests authored for the specified module.'
5 | previous:
6 |   - 
7 | next:
8 |   -
9 | ---
10 | 
11 | # Generate Unit Tests
12 | 
13 | Trigger: /generate <source-file>
14 | 
15 | Purpose: Generate unit tests for a given source file.
16 | 
17 | You are a CLI assistant focused on helping contributors with the task: Generate unit tests for a given source file.
18 | 
19 | ## Steps
20 | 
21 | 1. Inspect `package.json` to identify the unit test framework, runner scripts, and any helper utilities required for the suite.
22 | 2. Review the target source file with `sed -n '1,400p' {{args}}` to catalog exported members, branching logic, and error handling paths that must be exercised.
23 | 3. Outline the test file structure (location, naming, setup/teardown) and propose arrange/act/assert cases that cover happy paths, edge cases, and failure scenarios.
24 | 4. Provide guidance on implementing the tests and how to validate them locally (e.g., `npm test -- <pattern>` or framework-specific commands).
25 | 
26 | ## Output
27 | 
28 | - Begin with a concise summary that restates the goal: Generate unit tests for a given source file.
29 | - List the recommended test files, describe each test case, and highlight coverage gaps they close.
30 | - Call out the command(s) to run the new tests and any fixtures or mocks required.
31 | - Document the evidence you used (e.g., `package.json`, specific functions/branches in the source file) so maintainers can trust the conclusion.
32 | 
33 | ## Example
34 | 
35 | **Input**
36 | 
37 | ```
38 | src/components/Button.tsx
39 | ```
40 | 
41 | **Output**
42 | 
43 | - Summary: Author React Testing Library unit tests for `Button` to cover rendering, disabled behavior, and click handling.
44 | - Create `src/components/__tests__/Button.test.tsx` that:
45 |   - Renders the button label and asserts it matches `props.children`.
46 |   - Verifies `onClick` fires once when the button is enabled and is skipped when `disabled` is true.
47 |   - Confirms the `variant="primary"` branch applies the `btn-primary` class.
48 | - Validation: Run `npm test -- Button.test.tsx` to execute the suite.
49 | - Evidence: `package.json` (scripts.test uses Jest + RTL), component branches in `src/components/Button.tsx` (disabled guard, variant styling).
```

02.postwork/problem-analyzer.md
```
1 | ---
2 | description: Analyze a problem by locating affected files, root cause, and proposing minimal fixes
3 | argument-hint: PROBLEM=<problem-description>
4 | ---
5 | 
6 | <problem>
7 | $PROBLEM
8 | </problem>
9 | 
10 | Tasks:
11 | 1) Locate all files/modules affected by the issue. List paths and why each is implicated.
12 | 2) Explain the root cause(s): what changed, how it propagates to the failure, and any environmental factors.
13 | 3) Propose the minimal, safe fix. Include code-level steps, side effects, and tests to add/update.
14 | 4) Flag any missing or outdated documentation/configs/schemas that should be updated or added (especially if code appears outdated vs. current behavior). Specify exact docs/sections to create or amend.
15 | 
16 | Output format:
17 | - Affected files:
18 |   - <path>: <reason>
19 | - Root cause:
20 |   - <concise explanation>
21 | - Proposed fix:
22 |   - <steps/patch outline>
23 |   - Tests:
24 | - Documentation gaps:
25 |   - <doc_section_what_to_update_add>
26 | - Open questions/assumptions:
27 |   - <items>
```

02.postwork/refactor-code.md
```
1 | ---
2 | description: Refactor code with a specific goal, keeping changes isolated
3 | argument-hint: GOAL=<refactoring-goal-description>
4 | ---
5 | 
6 | <refactoring_goal>
7 | $GOAL
8 | </refactoring_goal>
9 | 
10 | Task: <refactoring_goal>
11 | 
12 | - Keep the commit isolated to this feature.
13 | - Document but do not fix unrelated problems you find.
14 | - Never add fallbacks/backward-compability/feature flags, we are always build the full new refactored solution.
```

02.postwork/ui-screenshots.md
```
1 | ---
2 | phase: 'P4 Frontend UX'
3 | gate: 'Accessibility checks queued'
4 | status: 'capture UX issues and backlog fixes.'
5 | previous:
6 |   -
7 |   -
8 | next:
9 |   -
10 |   - 
11 | ---
12 | 
13 | # UI Screenshots
14 | 
15 | Trigger: /ui-screenshots
16 | 
17 | Purpose: Analyze screenshots for UI bugs or inspiration and propose actionable UI changes.
18 | 
19 | ## Steps
20 | 
21 | 1. Accept screenshot paths or links.
22 | 2. Describe visual hierarchy, spacing, contrast, and alignment issues.
23 | 3. Output concrete CSS or component changes.
24 | 
25 | ## Output format
26 | 
27 | - Issue list and code snippets to fix visuals.
```


--- docs/ai-sdk-provider-codex-cli/ai-sdk-provider-codex-cli-llms.txt ---
# Ai Sdk Provider Codex Cli

> This project serves as a community-built adapter that bridges the Vercel AI SDK v5 with OpenAI’s Codex CLI, allowing developers to leverage GPT-5.1 class models via the Codex CLI tool. It enables seamless integration of text generation, object generation with Zod schemas, and tool streaming for real-time monitoring of autonomous tool execution. The provider is designed for Node.js environments, supports CI/CD workflows, and offers granular control over model behavior, logging, and sandboxing, making it suitable for automation, large-scale applications, and enterprise-grade AI workflows.

**Remember:**
- Codex CLI
- GPT-5.1 class models
- AI SDK v5 (LanguageModelV2)
- Streaming and non-streaming text generation
- Tool streaming (real-time tool execution monitoring)
- Native JSON Schema validation via --output-schema

## Docs
- [Configuration](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/configuration.md): docs page.
- [Guide](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/guide.md): docs page.
- [Limitations](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/limitations.md): docs page.
- [Migration 0.2](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/migration-0.2.md): docs page.
- [Troubleshooting](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/troubleshooting.md): docs page.

## Tutorials
- [README](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/examples/README.md): install & quickstart.

## Optional
- [Changelog](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/CHANGELOG.md): version history.
- [Limitations](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/LIMITATIONS.md): docs page.
- [README](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/README.md): docs page.
- [Eslint.Config](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/eslint.config.js): docs page.
- [Tsup.Config](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/tsup.config.ts): docs page.
- [Vitest.Config](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/vitest.config.ts): docs page.

## Src
- [Codex Cli Language Model](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/codex-cli-language-model.ts): docs page.
- [Codex Cli Provider](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/codex-cli-provider.ts): docs page.
- [Errors](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/errors.ts): docs page.
- [Index](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/index.ts): docs page.
- [Logger](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/logger.ts): docs page.
- [Message Mapper](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/message-mapper.ts): docs page.
- [Types](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/types.ts): docs page.
- [Validation](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/validation.ts): docs page.
- [Codex Cli Language Model.Test](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/__tests__/codex-cli-language-model.test.ts): docs page.
- [Codex Cli Provider.Test](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/__tests__/codex-cli-provider.test.ts): docs page.


## Links discovered
- [Configuration](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/configuration.md)
- [Guide](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/guide.md)
- [Limitations](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/limitations.md)
- [Migration 0.2](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/migration-0.2.md)
- [Troubleshooting](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/troubleshooting.md)
- [README](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/examples/README.md)
- [Changelog](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/CHANGELOG.md)
- [Limitations](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/LIMITATIONS.md)
- [README](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/README.md)
- [Eslint.Config](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/eslint.config.js)
- [Tsup.Config](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/tsup.config.ts)
- [Vitest.Config](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/vitest.config.ts)
- [Codex Cli Language Model](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/codex-cli-language-model.ts)
- [Codex Cli Provider](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/codex-cli-provider.ts)
- [Errors](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/errors.ts)
- [Index](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/index.ts)
- [Logger](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/logger.ts)
- [Message Mapper](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/message-mapper.ts)
- [Types](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/types.ts)
- [Validation](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/validation.ts)
- [Codex Cli Language Model.Test](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/__tests__/codex-cli-language-model.test.ts)
- [Codex Cli Provider.Test](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/src/__tests__/codex-cli-provider.test.ts)

--- docs/ai-sdk-provider-codex-cli/ai-sdk-provider-codex-cli-llms-ctx.txt ---
<project title="Ai Sdk Provider Codex Cli" summary="This project serves as a community-built adapter that bridges the Vercel AI SDK v5 with OpenAI’s Codex CLI, allowing developers to leverage GPT-5.1 class models via the Codex CLI tool. It enables seamless integration of text generation, object generation with Zod schemas, and tool streaming for real-time monitoring of autonomous tool execution. The provider is designed for Node.js environments, supports CI/CD workflows, and offers granular control over model behavior, logging, and sandboxing, making it suitable for automation, large-scale applications, and enterprise-grade AI workflows.">**Remember:**
- Codex CLI
- GPT-5.1 class models
- AI SDK v5 (LanguageModelV2)
- Streaming and non-streaming text generation
- Tool streaming (real-time tool execution monitoring)
- Native JSON Schema validation via --output-schema<docs><doc title="Configuration" desc="docs page."># Configuration Reference

This provider wraps the `codex exec` CLI in non‑interactive mode and maps settings to CLI flags/config overrides.

## Settings

- `allowNpx` (boolean): If true, runs `npx -y @openai/codex` when Codex isn’t found on PATH.
- `codexPath` (string): Explicit path to Codex JS entry (`bin/codex.js`), bypassing PATH resolution.
- `cwd` (string): Working directory for the spawned process.
- `addDirs` (string[]): Additional directories Codex can read/write. Emits one `--add-dir <path>` per entry (useful in monorepos or when sharing resources across packages).
- `color` ('always' | 'never' | 'auto'): Controls ANSI color emission.
- `skipGitRepoCheck` (boolean): When true, passes `--skip-git-repo-check`.
- `fullAuto` (boolean): Sets `--full-auto` (low-friction sandboxed execution).
- `dangerouslyBypassApprovalsAndSandbox` (boolean): Maps to `--dangerously-bypass-approvals-and-sandbox`.
- `approvalMode` ('untrusted' | 'on-failure' | 'on-request' | 'never'): Applied via `-c approval_policy=...`.
- `sandboxMode` ('read-only' | 'workspace-write' | 'danger-full-access'): Applied via `-c sandbox_mode=...`.
- `outputLastMessageFile` (string): File path to write the last agent message. If omitted, a temp file is created.
- `env` (Record<string,string>): Extra env vars for the child process (e.g., `OPENAI_API_KEY`).
- `verbose` (boolean): Enable verbose logging mode. When `true`, enables `debug` and `info` log levels. When `false` (default), only `warn` and `error` are logged.
- `logger` (Logger | false): Custom logger object or `false` to disable logging entirely. Logger must implement four methods: `debug`, `info`, `warn`, and `error`. Default uses `console.*` methods.
- `rmcpClient` (boolean): Enable the RMCP client so HTTP-based MCP servers can be reached (`-c features.rmcp_client=true`).
- `mcpServers` (Record<string, McpServerConfig>): Define MCP servers (stdio or HTTP). Keys are server names; values follow the shapes below.

## Model Parameters & Advanced Options (v0.4.0+)

### Reasoning & Verbosity

- **`reasoningEffort`** ('minimal' | 'low' | 'medium' | 'high' | 'xhigh'): Controls reasoning depth for reasoning-capable models (o3, o4-mini, the GPT-5.1 family, and legacy GPT-5). Higher effort produces more thorough reasoning at the cost of latency. Maps to `-c model_reasoning_effort=<value>`.
  - Per the Codex CLI model preset definitions (`codex-rs/common/src/model_presets.rs`), `gpt-5.1` and `gpt-5.1-codex` expose `low`, `medium`, and `high`; `gpt-5.1-codex-max` adds `xhigh`; and `gpt-5.1-codex-mini` only surfaces `medium` and `high`.
  - The older `gpt-5` slug still exposed `minimal`, but the GPT-5.1 family does not; passing `minimal` to a GPT-5.1 slug is rejected server-side.
- **`reasoningSummary`** ('auto' | 'detailed'): Controls reasoning summary detail level. **Note:** Despite API error messages claiming 'concise' and 'none' are valid, they are rejected with 400 errors. Only 'auto' and 'detailed' work. Maps to `-c model_reasoning_summary=<value>`.
- **`reasoningSummaryFormat`** ('none' | 'experimental'): Controls reasoning summary format (experimental). Maps to `-c model_reasoning_summary_format=<value>`.
- **`modelVerbosity`** ('low' | 'medium' | 'high'): Controls output length/detail for GPT-5.1 **non-Codex** models (and legacy GPT-5). Codex-specific slugs (`gpt-5.1-codex`, `gpt-5.1-codex-mini`) ignore this, because the CLI disables verbosity for those model families (`codex-rs/core/src/model_family.rs`). Maps to `-c model_verbosity=<value>` when supported.

### Advanced Codex Features

- **`includePlanTool`** (boolean): Include experimental plan tool that the model can use to update its current plan. Maps to `--include-plan-tool`.
- **`profile`** (string): Configuration profile from config.toml to specify default options. Maps to `--profile <name>`.
- **`oss`** (boolean): Use OSS provider (experimental). Maps to `--oss`.
- **`webSearch`** (boolean): Enable web search tool for the model. Maps to `-c tools.web_search=true`.

### MCP Servers (v0.6.0+)

- **`rmcpClient`** (boolean): Enables the RMCP client for HTTP-based MCP servers. Maps to `-c features.rmcp_client=true`.
- **`mcpServers`** (Record<string, McpServerConfig>): Define MCP servers by name.
  - Common fields: `enabled?`, `startupTimeoutSec?`, `toolTimeoutSec?`, `enabledTools?`, `disabledTools?`.
  - **Stdio servers** (`transport: 'stdio'`): `command` (required), `args?`, `env?`, `cwd?`.
  - **HTTP/RMCP servers** (`transport: 'http'`): `url` (required), `bearerToken?`, `bearerTokenEnvVar?`, `httpHeaders?`, `envHttpHeaders?`.

Example:

```ts
const model = codexCli('gpt-5.1-codex', {
  rmcpClient: true,
  mcpServers: {
    // Stdio MCP
    repo: {
      transport: 'stdio',
      command: 'node',
      args: ['tools/repo-mcp.js'],
      env: { API_KEY: process.env.REPO_KEY ?? '' },
      enabledTools: ['list', 'read'],
    },
    // HTTP/RMCP
    docs: {
      transport: 'http',
      url: 'https://mcp.internal/api',
      bearerTokenEnvVar: 'MCP_BEARER',
      httpHeaders: { 'x-tenant': 'acme' },
    },
  },
});
```

### Generic Config Overrides

- **`configOverrides`** (Record<string, string | number | boolean | object>): Generic Codex CLI config overrides. Allows setting any config value without updating the provider. Each entry maps to `-c <key>=<value>`.

Examples (nested objects are flattened to dotted keys):

```typescript
{
  experimental_resume: '/tmp/session.jsonl',           // string
  hide_agent_reasoning: true,                          // boolean
  model_context_window: 200000,                        // number
  sandbox_workspace_write: { network_access: true },   // object → -c sandbox_workspace_write.network_access=true
  'model_providers.custom.base_url': 'http://localhost:8000'  // nested config path
}
```

Values are serialized:

- string → raw string
- number/boolean → String(value)
- object → flattened to dotted keys (recursively)
- array → JSON.stringify(value)
- non-plain objects (Date, RegExp, Map, etc.) → JSON.stringify(value)

### Per-call Overrides (`providerOptions`, v0.4.0+)

Use AI SDK `providerOptions` to override Codex parameters for a single request without modifying the
model instance. The provider parses the `codex-cli` entry and applies the keys below:

- `reasoningEffort` → `model_reasoning_effort`
- `reasoningSummary` → `model_reasoning_summary`
- `reasoningSummaryFormat` → `model_reasoning_summary_format`
- `textVerbosity` → `model_verbosity` (AI SDK naming; mirrors constructor `modelVerbosity`)
- `addDirs` → appends `--add-dir` entries (merged with constructor `addDirs`)
- `configOverrides` → merged with constructor-level overrides (per-call values win on key conflicts)
- `mcpServers` → merged with constructor-level MCP servers (per-call values override per server)
- `rmcpClient` → overrides constructor `rmcpClient`

```ts
import { generateText } from 'ai';
import { codexCli } from 'ai-sdk-provider-codex-cli';

const model = codexCli('gpt-5.1', {
  reasoningEffort: 'medium',
  modelVerbosity: 'medium',
});

await generateText({
  model,
  prompt: 'Compare the trade-offs of high vs. low verbosity.',
  providerOptions: {
    'codex-cli': {
      reasoningEffort: 'high',
      reasoningSummary: 'detailed',
      textVerbosity: 'high',
      configOverrides: {
        'sandbox_workspace_write.network_access': true,
      },
    },
  },
});
```

**Precedence:** `providerOptions['codex-cli']` > constructor `CodexCliSettings` > Codex CLI defaults.

## Defaults & Recommendations

- Non‑interactive defaults:
  - `approvalMode: 'on-failure'`
  - `sandboxMode: 'workspace-write'`
  - `skipGitRepoCheck: true`
- For strict automation in controlled environments:
  - `fullAuto: true` OR `dangerouslyBypassApprovalsAndSandbox: true` (be careful!)

## Flag Mapping

### Core Settings

- `approvalMode` → `-c approval_policy=<mode>`
- `sandboxMode` → `-c sandbox_mode=<mode>`
- `skipGitRepoCheck` → `--skip-git-repo-check`
- `fullAuto` → `--full-auto`
- `dangerouslyBypassApprovalsAndSandbox` → `--dangerously-bypass-approvals-and-sandbox`
- `color` → `--color <always|never|auto>`
- `outputLastMessageFile` → `--output-last-message <path>`
- `addDirs` → `--add-dir <path>` (emitted once per entry)

### Model Parameters (v0.4.0+)

- `reasoningEffort` → `-c model_reasoning_effort=<value>`
- `reasoningSummary` → `-c model_reasoning_summary=<value>`
- `reasoningSummaryFormat` → `-c model_reasoning_summary_format=<value>`
- `modelVerbosity` → `-c model_verbosity=<value>`
- `includePlanTool` → `--include-plan-tool`
- `profile` → `--profile <name>`
- `oss` → `--oss`
- `webSearch` → `-c tools.web_search=true`
- `configOverrides` → `-c <key>=<value>` (for each entry)

### MCP

- `rmcpClient` → `-c features.rmcp_client=true`
- `mcpServers` → `-c mcp_servers.<name>.<field>=<value>` for each field (e.g., `command`, `args`, `env.KEY`, `url`, `bearer_token_env_var`, `http_headers.Header-Name`).

## JSON Mode (v0.2.0+)

When the AI SDK request uses `responseFormat: { type: 'json' }`, the provider:

1. Converts your Zod schema to JSON Schema format
2. Sanitizes the schema (removes unsupported fields like `format`, `pattern`, `$schema`, etc.)
3. Passes the schema via `--output-schema` for native OpenAI strict mode enforcement
4. The API returns guaranteed valid JSON matching your schema
5. AI SDK validates the response with Zod

**Breaking change from v0.1.x**: No longer uses prompt engineering. Schemas are enforced at the API level using OpenAI strict mode, which does not support optional fields or format validators.</doc><doc title="Guide" desc="docs page."># Codex CLI Provider – AI SDK v5 Guide

This guide explains how to use the Codex CLI provider with Vercel AI SDK v5 for text generation, streaming, and JSON object generation.

## Getting Started

1. Install Codex CLI and authenticate:

```bash
npm i -g @openai/codex
codex login   # or set OPENAI_API_KEY
```

2. Install AI SDK and this provider:

```bash
npm i ai ai-sdk-provider-codex-cli
```

## Basic Usage

```js
import { generateText, streamText, generateObject } from 'ai';
import { codexCli } from 'ai-sdk-provider-codex-cli';
import { z } from 'zod';

const model = codexCli('gpt-5.1-codex', {
  allowNpx: true,
  skipGitRepoCheck: true,
  approvalMode: 'on-failure',
  sandboxMode: 'workspace-write',
});

// Text
const { text } = await generateText({ model, prompt: 'Say hello in one word.' });

// Streaming
const { textStream } = await streamText({ model, prompt: 'Two short lines.' });
for await (const chunk of textStream) process.stdout.write(chunk);

// Object (JSON)
const schema = z.object({ name: z.string(), age: z.number().int() });
const { object } = await generateObject({ model, schema, prompt: 'Generate a user.' });
```

## Conversation History

Use AI SDK messages to retain context:

```js
const messages = [
  { role: 'user', content: 'My name is Dana.' },
  { role: 'assistant', content: 'Hi Dana!' },
  { role: 'user', content: 'What did I just tell you my name was?' },
];
const { text } = await generateText({ model, messages });
```

## Structured Output (JSON)

**v0.2.0+**: The provider uses native `--output-schema` support with OpenAI strict mode for API-level JSON enforcement. Schemas are passed directly to the API, eliminating 100-200 tokens per request and improving reliability.

**⚠️ Important Limitations:**

- Optional fields are **NOT supported** by OpenAI strict mode (all fields must be required)
- Format validators (`.email()`, `.url()`, `.uuid()`) are stripped (use descriptions instead)
- Pattern validators (`.regex()`) are stripped (use descriptions instead)

See [LIMITATIONS.md](../../LIMITATIONS.md) for full details.

Tips:

- Add clear field descriptions to your Zod schema (especially for format hints like "UUID format", "YYYY-MM-DD date")
- All fields must be required (no `.optional()`)
- Use descriptions instead of format validators
- Keep constraints realistic for better adherence

## Permissions & Sandbox

The provider applies safe defaults for non‑interactive execution. You can override them per call via provider settings:

- `fullAuto: true` → `--full-auto`
- `dangerouslyBypassApprovalsAndSandbox: true` → `--dangerously-bypass-approvals-and-sandbox`
- Otherwise, the provider writes config overrides: `-c approval_policy=...` and `-c sandbox_mode=...`.

Recommended defaults for CI/local automation:

- `approvalMode: 'on-failure'`
- `sandboxMode: 'workspace-write'`
- `skipGitRepoCheck: true`

## Streaming Behavior

**Status:** Incremental streaming not currently supported with `--experimental-json` format (expected in future Codex CLI releases)

The `--experimental-json` output format (introduced Sept 25, 2025) currently only emits `item.completed` events with full text content. Incremental streaming via `item.updated` or delta events is not yet implemented by OpenAI.

**What this means:**

- `streamText()` works functionally but delivers the entire response in a single chunk after generation completes
- No incremental text deltas—you wait for the full response, then receive it all at once
- The AI SDK's streaming interface is supported, but actual incremental streaming is not available

**How the provider handles this:**

1. Emits `response-metadata` stream part when the session is configured
2. Waits for `item.completed` event with the final assistant message
3. Emits a single `text-delta` with the full text
4. Emits `finish`

**Future support:** The Codex CLI commit (344d4a1d) introducing experimental JSON explicitly notes: "or other item types like `item.output_delta` when we need streaming" and states "more event types and item types to come."

When OpenAI adds streaming support, this provider will be updated to handle those events and enable true incremental streaming. Your code using the AI SDK stream API will remain compatible.

## Logging Configuration

Control how the provider logs execution information, warnings, and errors. The logger supports multiple log levels and a verbose mode for detailed debugging.

### Log Levels

The provider supports four log levels:

- **`debug`**: Detailed execution tracing (request/response, tool calls, stream events)
- **`info`**: General execution flow information (session initialization, completion)
- **`warn`**: Warnings about configuration issues or unexpected behavior
- **`error`**: Error messages for failures and exceptions

### Basic Configuration

```typescript
import { createCodexCli } from 'ai-sdk-provider-codex-cli';

// Default: logs warnings and errors to console
const defaultCodex = createCodexCli();

// Disable all logging
const silentCodex = createCodexCli({
  defaultSettings: {
    logger: false,
  },
});

// Custom logger - must implement all four log levels
const customCodex = createCodexCli({
  defaultSettings: {
    logger: {
      debug: (message) => myLogger.debug('Codex:', message),
      info: (message) => myLogger.info('Codex:', message),
      warn: (message) => myLogger.warn('Codex:', message),
      error: (message) => myLogger.error('Codex:', message),
    },
  },
});

// Model-specific logger override
const model = customCodex('gpt-5.1-codex', {
  logger: false, // Disable logging for this model only
});
```

### Verbose Mode (Debug Logging)

Enable verbose mode to see detailed execution logs, including:

- Request/response tracing
- Tool execution lifecycle (tool calls, results, errors)
- Stream event processing
- Command execution details and token usage
- Session management

**Without verbose mode**, only `warn` and `error` messages are logged.
**With verbose mode enabled**, `debug` and `info` messages are also logged.

```typescript
import { createCodexCli } from 'ai-sdk-provider-codex-cli';

// Enable verbose logging for debugging
const codexWithDebug = createCodexCli({
  defaultSettings: {
    verbose: true, // Enable debug and info logging
  },
});

// Use with custom logger
const codexCustom = createCodexCli({
  defaultSettings: {
    verbose: true,
    logger: {
      debug: (msg) => console.log(`[DEBUG] ${msg}`),
      info: (msg) => console.log(`[INFO] ${msg}`),
      warn: (msg) => console.warn(`[WARN] ${msg}`),
      error: (msg) => console.error(`[ERROR] ${msg}`),
    },
  },
});

// Model-specific verbose override
const model = codexWithDebug('gpt-5.1-codex', {
  verbose: false, // Disable verbose for this specific model
});
```

### What Gets Logged in Verbose Mode

With `verbose: true`, you'll see intermediate process logs including:

**For `generateText()` calls:**

```
[DEBUG] [codex-cli] Starting doGenerate request with model: gpt-5.1-codex
[DEBUG] [codex-cli] Request mode: regular, response format: none
[DEBUG] [codex-cli] Converted 2 messages, response format: none
[DEBUG] [codex-cli] Executing Codex CLI: npx with 15 arguments, cwd: default
[DEBUG] [codex-cli] Received event type: thread.started
[DEBUG] [codex-cli] Session started: thread-abc123
[DEBUG] [codex-cli] Received event type: turn.completed
[INFO] [codex-cli] Request completed - Session: thread-abc123, Duration: 1523ms, Tokens: 373
[DEBUG] [codex-cli] Token usage - Input: 245, Output: 128, Total: 373
```

**For `streamText()` calls with tools:**

```
[DEBUG] [codex-cli] Starting doStream request with model: gpt-5.1-codex
[DEBUG] [codex-cli] Converted 1 messages for streaming, response format: none
[DEBUG] [codex-cli] Executing Codex CLI for streaming: npx with 14 arguments
[DEBUG] [codex-cli] Stream event: thread.started
[DEBUG] [codex-cli] Stream session started: thread-xyz789
[DEBUG] [codex-cli] Stream event: item.started
[DEBUG] [codex-cli] Tool detected: exec, item type: command_execution
[DEBUG] [codex-cli] Emitting tool invocation: exec
[DEBUG] [codex-cli] Stream event: item.completed
[DEBUG] [codex-cli] Tool completed: exec
[DEBUG] [codex-cli] Received assistant message, length: 142
[INFO] [codex-cli] Stream completed - Session: thread-xyz789, Duration: 3241ms, Tokens: 768
[DEBUG] [codex-cli] Token usage - Input: 512, Output: 256, Total: 768
```

### Logger Options

- `undefined` (default): Uses `console.debug`, `console.info`, `console.warn`, and `console.error`
- `false`: Disables all logging
- Custom `Logger` object: Must implement `debug`, `info`, `warn`, and `error` methods

### Combining with Error Handling

For comprehensive debugging, combine verbose logging with error handling:

```typescript
import { createCodexCli } from 'ai-sdk-provider-codex-cli';
import { generateText } from 'ai';

const codexCli = createCodexCli({
  defaultSettings: {
    verbose: true,
    logger: {
      debug: (msg) => myLogger.debug(msg),
      info: (msg) => myLogger.info(msg),
      warn: (msg) => myLogger.warn(msg),
      error: (msg) => myLogger.error(msg),
    },
  },
});

try {
  const result = await generateText({
    model: codexCli('gpt-5.1-codex'),
    prompt: 'Hello!',
  });
} catch (error) {
  console.error('Generation failed:', error);
  // Check error.data for additional context (exitCode, stderr, etc.)
  if (error.data) {
    console.error('Error details:', error.data);
  }
}
```

## Examples

See `examples/` for runnable scripts that cover:

- Basic text generation and streaming
- Conversation history and system messages
- Permissions & sandbox modes
- JSON object generation: basic, nested, constraints, advanced</doc><doc title="Limitations" desc="docs page."># Limitations

## Runtime & Architecture

- Node.js runtime only (spawns a local process); Edge runtimes are not supported.
- Image inputs are not supported.

## Streaming Behavior

- Codex `--experimental-json` mode emits events (`thread.started`, `turn.completed`, `item.completed`) rather than streaming text deltas; streaming usually returns a final chunk. The CLI provides the final assistant content in the `item.completed` event, which this provider reads and emits at the end.

## Tool Streaming (v0.3.0+)

- Tool streaming is fully supported - tool invocation and result events are emitted in real-time
- **Limitation:** Real-time output streaming (`output-delta` events) not yet available. Tool outputs are delivered in the final `tool-result` event via `aggregatedOutput` field, not as incremental deltas during tool execution
- This limitation exists because Codex CLI's experimental JSON format doesn't currently emit incremental output events during tool execution

## JSON Schema (v0.2.0+)

- **Optional fields NOT supported**: OpenAI strict mode requires all fields to be required (no `.optional()`)
- **Format validators stripped**: `.email()`, `.url()`, `.uuid()` are removed during sanitization (use descriptions instead)
- **Pattern validators stripped**: `.regex()` is removed during sanitization (use descriptions instead)
- See [LIMITATIONS.md](../../LIMITATIONS.md) at repo root for comprehensive details

## AI SDK Parameter Support

- Some AI SDK parameters are not applicable to Codex CLI (e.g., temperature, topP, penalties). The provider surfaces warnings and ignores them.

## Model Parameter Validation (v0.4.0+)

**Known API Quirks:**

### reasoningSummary Parameter

The OpenAI Responses API has misleading error messages for the `reasoningSummary` parameter:

- **Valid values:** `'auto'`, `'detailed'`
- **Invalid values:** `'concise'`, `'none'` (rejected with 400 errors)

**The quirk:** When you pass an invalid value like `'none'`, the API error claims valid values are `'concise', 'detailed', and 'auto'`. However, if you then try `'concise'`, the API rejects it with: `"Unsupported value: 'concise' is not supported with the 'gpt-5.1-codex' model."`

This provider's type system and validation only allow `'auto'` and `'detailed'` to prevent runtime errors.

## Observability

- Token usage tracking is available via `turn.completed` events (requires Codex CLI >= 0.44.0)
- Earlier versions (< 0.44.0) will report 0 for all token counts</doc><doc title="Migration 0.2" desc="docs page."># Migration Guide: v0.1.x → v0.2.0

This guide helps you migrate from v0.1.x to v0.2.0 of the AI SDK Provider for Codex CLI.

## Overview

Version 0.2.0 introduces **breaking changes** that significantly improve reliability and token efficiency by leveraging Codex CLI's native JSON schema support.

## Breaking Changes

### 1. Native Schema Support Only

**What changed:**

- Removed prompt engineering for JSON generation
- Removed `extract-json.ts` extraction logic
- All JSON output now uses `--output-schema` (API-enforced)

**Impact:**

- ✅ **Better**: 100-200 fewer tokens per JSON request
- ✅ **Better**: More reliable structured output (API-level enforcement)
- ✅ **Better**: No more manual JSON extraction

**Migration steps:**

- ⚠️ **IMPORTANT**: Check all Zod schemas for unsupported features (see below)
- Remove any manual JSON instructions you added to prompts (they're redundant now)

### OpenAI Strict Mode Limitations

**What changed:**

- OpenAI strict mode does NOT support optional fields
- Format validators (`.email()`, `.url()`, `.uuid()`) are stripped
- Pattern validators (`.regex()`) are stripped

**Impact:**

- ⚠️ **Breaking**: Schemas with `.optional()` will fail
- ⚠️ **Breaking**: Format validators are ignored (no validation)
- ⚠️ **Breaking**: Pattern validators are ignored (no validation)

**Migration steps:**

1. Remove all `.optional()` calls - make fields required or use empty string/null defaults
2. Replace format validators with descriptions:
   - `.email()` → `.describe('Valid email address')`
   - `.url()` → `.describe('Valid URL')`
   - `.uuid()` → `.describe('UUID format')`
3. Replace pattern validators with descriptions:
   - `.regex(/^\d{4}-\d{2}-\d{2}$/)` → `.describe('Date in YYYY-MM-DD format')`

**Example:**

```javascript
// ❌ Before (v0.1.x) - WILL FAIL in v0.2.0
const schema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  website: z.string().url().optional(),
  date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
});

// ✅ After (v0.2.0) - Works with OpenAI strict mode
const schema = z.object({
  id: z.string().describe('UUID format'),
  email: z.string().describe('Valid email address'),
  website: z.string().describe('Personal website URL (or empty string if none)'),
  date: z.string().describe('Date in YYYY-MM-DD format'),
});
```

See [LIMITATIONS.md](../../LIMITATIONS.md) for comprehensive details.

### 2. New Event Format

**What changed:**

- Switched from `--json` to `--experimental-json`
- Event structure changed from old format

**Old format (v0.1.x):**

```json
{
  "id": "evt_123",
  "msg": {
    "type": "session_configured",
    "session_id": "abc123"
  }
}
```

**New format (v0.2.0+):**

```json
{
  "type": "session.created",
  "session_id": "abc123"
}
```

**Impact:**

- ✅ **Better**: Structured event types
- ✅ **Better**: Usage tracking from `turn.completed` events
- ⚠️ **Breaking**: Event structure is different (handled internally by provider)

**Migration steps:**

- No action needed if you're using the high-level AI SDK APIs (`generateText`, `generateObject`, etc.)
- If you were parsing raw events, update to new format

### 3. Simplified Internal API

**What changed:**

- Removed `mode` parameter from `mapMessagesToPrompt`
- Removed `jsonSchema` parameter from `mapMessagesToPrompt`
- Removed `PromptMode` type

**Impact:**

- ⚠️ **Breaking**: If you were importing and using `mapMessagesToPrompt` directly
- ✅ **Better**: Simpler internal API

**Migration steps:**

- If you weren't using internal APIs: no action needed
- If you were: update to simplified signature (just pass `prompt`)

## Benefits of v0.2.0

### Token Efficiency

**Before (v0.1.x):**

```
Prompt: CRITICAL: You MUST respond with ONLY a JSON object. NO other text.
Your response MUST start with { and end with }
The JSON MUST match this EXACT schema:
{
  "type": "object",
  "properties": { ... }
}

Now, based on the following conversation, generate ONLY the JSON object:

[Your actual prompt here]
```

**Cost:** ~150 extra tokens

**After (v0.2.0):**

```
[Your actual prompt here]
```

**Cost:** 0 extra tokens (schema passed via `--output-schema`)

### Reliability

**Before (v0.1.x):**

- Prompt engineering → model sometimes adds text outside JSON
- Manual extraction → brittle brace-counting logic
- No API-level enforcement → model can deviate from schema

**After (v0.2.0):**

- Native schema → API enforces with `strict: true`
- No extraction needed → guaranteed valid JSON
- Model cannot deviate from schema

### Code Simplicity

**Removed:**

- ~50 lines of prompt engineering logic
- ~20 lines of JSON extraction logic
- Multiple code paths for different modes

**Result:**

- Simpler, more maintainable codebase
- Fewer edge cases
- Better error handling

## Migration Checklist

### For Most Users

- [ ] Update package: `npm install ai-sdk-provider-codex-cli@0.2.0`
- [ ] **CRITICAL**: Review ALL Zod schemas:
  - [ ] Remove all `.optional()` calls
  - [ ] Replace `.email()`, `.url()`, `.uuid()` with `.describe()`
  - [ ] Replace `.regex()` with `.describe()`
- [ ] Test your existing `generateObject` calls
- [ ] Review prompts and remove any manual JSON instructions
- [ ] Verify output matches expectations
- [ ] Celebrate improved token efficiency! 🎉

### For Advanced Users (using internal APIs)

- [ ] Update `mapMessagesToPrompt` calls if you were using it directly
- [ ] Update event parsing if you were parsing raw CLI events
- [ ] Review custom integrations

### For Library Authors

- [ ] Update to v0.2.0 in your dependencies
- [ ] Test integration with new event format
- [ ] Update documentation to reflect changes

## Example: Before & After

### Before (v0.1.x)

```javascript
import { generateObject } from 'ai';
import { codexCli } from 'ai-sdk-provider-codex-cli';
import { z } from 'zod';

const schema = z.object({
  name: z.string(),
  age: z.number().int(),
});

// Provider adds ~150 tokens of JSON instructions to prompt
const { object } = await generateObject({
  model: codexCli('gpt-5'),
  schema,
  prompt: 'Generate a person profile',
});
```

### After (v0.2.0)

```javascript
import { generateObject } from 'ai';
import { codexCli } from 'ai-sdk-provider-codex-cli';
import { z } from 'zod';

const schema = z.object({
  name: z.string(),
  age: z.number().int(),
});

// Provider passes schema via --output-schema (0 extra tokens!)
const { object } = await generateObject({
  model: codexCli('gpt-5'),
  schema,
  prompt: 'Generate a person profile',
});
```

**Result:** Same API, same code, but:

- 150 fewer tokens per request
- More reliable output
- Faster responses

## Troubleshooting

### Issue: "Invalid JSON" errors

**Cause:** Shouldn't happen with native schema enforcement

**Solution:**

1. Verify you're on v0.2.0: `npm list ai-sdk-provider-codex-cli`
2. Check Codex CLI version: `codex --version` (should support `--output-schema`)
3. Report issue with example if problem persists

### Issue: Different output format than expected

**Cause:** Schema validation is now stricter (API-level enforcement)

**Solution:**

1. Review your Zod schema
2. Check for required fields
3. Verify enum values match expectations
4. Use examples to test schema design

### Issue: "Unknown flag: --output-schema"

**Cause:** Your Codex CLI version is too old

**Solution:**

1. Update Codex CLI: `npm install -g @openai/codex@latest`
2. Or use Homebrew: `brew upgrade codex`
3. Verify: `codex --version`

### Issue: Missing usage stats

**Cause:** Usage stats come from `turn.completed` events in experimental JSON format

**Solution:**

1. Verify the provider is using `--experimental-json` (automatic in v0.2.0)
2. Check that Codex CLI is returning usage in events
3. Enable debug logging if needed

## Getting Help

- 📖 Read the [full documentation](./guide.md)
- 🐛 Report issues on [GitHub](https://github.com/ben-vargas/ai-sdk-provider-codex-cli/issues)
- 💬 Ask questions in discussions
- 📝 Check the [CHANGELOG](../../CHANGELOG.md) for detailed changes

## What's Next?

With v0.2.0 stable, future improvements may include:

- Better streaming support when Codex CLI adds it
- More granular event tracking
- Performance optimizations
- Additional configuration options

Stay tuned for updates!</doc><doc title="Troubleshooting" desc="docs page."># Troubleshooting

## "codex not found" / CLI not on PATH

- Install globally: `npm i -g @openai/codex`
- Or enable fallback: `{ allowNpx: true }` (uses `npx -y @openai/codex`)

## Not authenticated / 401 / "Please login"

- Run `codex login`
- Ensure `~/.codex/auth.json` exists and is readable
- Alternatively set `OPENAI_API_KEY` in `env`

## Sandbox / approval errors

- Use safer defaults for non‑interactive runs:
  - `approvalMode: 'on-failure'`
  - `sandboxMode: 'workspace-write'`
  - `skipGitRepoCheck: true`
- For fully autonomous flows: `fullAuto: true` (be cautious). Avoid `dangerouslyBypassApprovalsAndSandbox` unless the environment is already sandboxed.

## Streaming emits only a final chunk

- Codex `--experimental-json` mode emits events (`session.created`, `turn.completed`, `item.completed`) rather than streaming text deltas; the provider still uses AI SDK's standard stream API. This is expected.

## Object generation fails with empty response

**v0.2.0+**: The provider uses native `--output-schema` with OpenAI strict mode. Common issues:

- **Optional fields**: Remove all `.optional()` calls - OpenAI strict mode requires all fields
- **Format validators**: Remove `.email()`, `.url()`, `.uuid()` - use descriptions like "Valid email address" or "UUID format" instead
- **Pattern validators**: Remove `.regex()` - use descriptions like "YYYY-MM-DD format" instead

See [LIMITATIONS.md](../../LIMITATIONS.md) for full details.

## zod v3/v4 compatibility warnings

- NPM warnings may appear due to transitive peers (e.g., `zod-to-json-schema`). They do not affect functionality. The provider works with `zod@^3` and `^4`.</doc></docs><tutorials><doc title="README" desc="install &amp; quickstart."># Codex CLI Provider Examples

This folder showcases how to use the AI SDK Codex CLI provider in practical scenarios. Each example is small, focused, and explains why it matters.

## Prerequisites

- Install and authenticate the Codex CLI:
  - `npm i -g @openai/codex`
  - `codex login` (ChatGPT OAuth) or set `OPENAI_API_KEY` for API auth
- Build the provider: `npm run build`

Tip: All examples set `allowNpx: true`, so they work even if `codex` is not on PATH. The provider is Node-only (it spawns a process), so run these in a Node environment (not Edge).

## How To Run

Run any example from the repo root:

```bash
npm run build
node examples/<file>.mjs
```

## Core Usage

- **basic-usage.mjs:** Minimal generation
  - Purpose: Prove setup works and show the smallest possible call.
  - Demonstrates: `generateText`, provider wiring, safe defaults.
  - Value: Quick sanity check to confirm your environment is correct.

- **basic-usage-gpt-5.1-codex.mjs:** Minimal generation with the Codex-optimized GPT-5.1 slug
  - Purpose: Confirm the provider works unchanged with the Codex-specific GPT-5.1 model ID.
  - Demonstrates: Same call path as above, but with the Codex slug so you can sanity check quickly.
  - Value: Handy regression test when Codex CLI ships new model identifiers.

- **streaming.mjs:** Stream responses
  - Purpose: Show the AI SDK streaming API shape.
  - Demonstrates: Reading `textStream` and rendering as chunks.
  - Value: Build responsive UIs. **Note:** `--experimental-json` format currently doesn't support incremental streaming—you'll receive the full response in a single chunk. The streaming API pattern is correct and will work when OpenAI adds delta event support to Codex CLI.

- **streaming-gpt-5.1-codex.mjs:** Streaming with the `gpt-5.1-codex` slug
  - Purpose: Validate stream handling with the Codex-specific model identifier.
  - Demonstrates: Same stream plumbing while calling the Codex slug.
  - Value: Confidence that streaming stays compatible across Codex model updates. **Note:** Currently delivers full response in single chunk due to experimental JSON format limitations.

- **conversation-history.mjs:** Maintain context
  - Purpose: Keep multi-turn state using a message array.
  - Demonstrates: AI SDK message roles (`user`, `assistant`).
  - Value: Realistic chat patterns where prior turns matter.

- **system-messages.mjs:** Control behavior
  - Purpose: Use system prompts to steer tone or format.
  - Demonstrates: `system` role to enforce concise or structured replies.
  - Value: Consistency across outputs without repeating instructions.

- **system-messages-gpt-5.1-codex.mjs:** System prompts with `gpt-5.1-codex`
  - Purpose: Mirror the system prompt example against the Codex slug to ensure compatibility.
  - Demonstrates: That the conversation mapper/system validation still behaves the same.
  - Value: Fast compatibility regression check for future Codex CLI updates.

- **custom-config.mjs:** Configure runtime
  - Purpose: Customize CWD and autonomy/sandbox policies per run.
  - Demonstrates: `cwd`, `approvalMode`, `sandboxMode`, `fullAuto` toggles.
  - Value: Balance safety vs. friction for local dev or CI use.

- **permissions-and-sandbox.mjs:** Compare modes
  - Purpose: Understand autonomy levels and sandbox modes.
  - Demonstrates: `on-failure`, `workspace-write`, `fullAuto`, and `dangerouslyBypassApprovalsAndSandbox`.
  - Value: Pick the right guardrails for your workflow. Warning: bypass is dangerous; prefer sandboxed modes unless you fully trust the environment.

- **advanced-settings.mjs:** Constructor-level model parameters (v0.4.0+)
  - Purpose: Demonstrate comprehensive reasoning controls and advanced Codex features at model creation.
  - Demonstrates: `reasoningEffort`, `reasoningSummary`, `webSearch`, `includePlanTool`, `profile`, `mcpServers`, `rmcpClient`, and `configOverrides`. (Swap to the non-Codex `gpt-5.1` slug if you need `modelVerbosity`.)
  - Value: See all Phase 1 parameters in action—configure behavior once at construction for consistent settings across all calls.

- **provider-options.mjs:** Per-call overrides (v0.4.0+)
  - Purpose: Show how to override reasoning and config settings for individual requests.
  - Demonstrates: `providerOptions['codex-cli']` with `reasoningEffort`, `reasoningSummary`, `textVerbosity`, `mcpServers`, `rmcpClient`, and `configOverrides`.
  - Value: Tune behavior dynamically (e.g., low-effort quick checks vs. high-effort deep dives) without cloning model instances.

## Reliability & Operations

- **long-running-tasks.mjs:** Abort and timeouts
  - Purpose: Cancel long operations cleanly.
  - Demonstrates: `AbortController` with AI SDK calls.
  - Value: Keep apps responsive and prevent runaway tasks.

- **error-handling.mjs:** Catch and classify errors
  - Purpose: Handle auth and general failures gracefully.
  - Demonstrates: Using `isAuthenticationError`, reading provider warnings.
  - Value: User-friendly errors (e.g., suggest `codex login`) and robust UX.

- **check-cli.mjs:** Troubleshoot setup
  - Purpose: Verify Codex binary and authentication status.
  - Demonstrates: Calling `codex --version` and `codex login status` (or `npx`).
  - Value: Quick diagnosis for PATH/auth issues.

- **limitations.mjs:** Understand unsupported settings
  - Purpose: Show which AI SDK knobs are ignored by Codex.
  - Demonstrates: Warnings for temperature/topP/topK/penalties/stop sequences.
  - Value: Avoid confusion and tune your prompts instead.

## Logging

- **logging-default.mjs:** Default logging behavior
  - Purpose: Show the default non-verbose logging mode.
  - Demonstrates: Only warn and error messages are logged, debug/info suppressed.
  - Value: Clean output for production—only essential logs appear.

- **logging-verbose.mjs:** Verbose mode for debugging
  - Purpose: Enable detailed execution logs for troubleshooting.
  - Demonstrates: All log levels (debug, info, warn, error) with full visibility.
  - Value: Development and debugging—see exactly what the provider is doing internally.

- **logging-custom-logger.mjs:** Custom logger integration
  - Purpose: Integrate with external logging systems (Winston, Pino, Datadog, etc.).
  - Demonstrates: Custom logger object with timestamps and prefixes.
  - Value: Route logs to your observability stack, format messages your way.

- **logging-disabled.mjs:** Silent operation
  - Purpose: Completely disable all provider logging.
  - Demonstrates: Setting `logger: false` for zero log output.
  - Value: Production scenarios where logs interfere with output processing.
  - Warning: No warnings or errors from the provider will be visible!

## Structured Output (Objects)

**v0.2.0+**: The provider uses native `--output-schema` support with OpenAI strict mode for API-level JSON enforcement. No prompt engineering needed—schemas are passed directly to the API, eliminating 100-200 tokens per request and improving reliability.

**⚠️ Important Limitations:**

- Optional fields are **NOT supported** by OpenAI strict mode (all fields must be required)
- Format validators (`.email()`, `.url()`, `.uuid()`) are stripped (use descriptions instead)
- Pattern validators (`.regex()`) are stripped (use descriptions instead)

See [LIMITATIONS.md](../LIMITATIONS.md) for full details.

- **generate-object-basic.mjs:** Fundamentals
  - Purpose: Start with simple, typed objects.
  - Demonstrates: Zod primitives, arrays, and numeric constraints.
  - Value: Cleanly typed responses for standard data collection.
  - Note: All fields must be required (no `.optional()`).

- **generate-object-basic-gpt-5.1-codex.mjs:** Fundamentals with `gpt-5.1-codex`
  - Purpose: Exercise JSON object generation against the Codex slug.
  - Demonstrates: Same Zod-driven prompts, proving compatibility with new identifiers.
  - Value: Quick regression path when Codex CLI ships new GPT-5 model slugs.

- **generate-object-nested.mjs:** Real-world hierarchies
  - Purpose: Work with nested objects and arrays of objects.
  - Demonstrates: Organization charts, product variants, nested specs.
  - Value: Match the shape of real app payloads and APIs.

- **generate-object-constraints.mjs:** Quality and validation
  - Purpose: Enforce enums, ranges, and constraints.
  - Demonstrates: Enums, min/max numeric constraints, string length constraints.
  - Value: Higher-quality data before it enters your system.
  - Note: Use descriptions for format hints (e.g., "UUID format", "YYYY-MM-DD date") since format/pattern validators are stripped.

- **generate-object-advanced.mjs:** Complex transformations
  - Purpose: Tackle richer tasks and data extraction.
  - Demonstrates: Product comparisons with scoring, HTML-to-JSON extraction, incident classification with recommendations.
  - Value: Turn free-form inputs into structured, actionable data.

- **generate-object-native-schema.mjs:** Native schema showcase (v0.2.0+)
  - Purpose: Demonstrate native `--output-schema` capabilities with API-level enforcement.
  - Demonstrates: Complex nested schemas, enums, constraints enforced by OpenAI strict mode.
  - Value: See the power of native schema support—no prompt engineering, 100-200 fewer tokens per request, guaranteed valid JSON.

## New in v0.2.0

- **experimental-json-events.mjs:** Event format showcase
  - Purpose: Understand the new `--experimental-json` event structure.
  - Demonstrates: `thread.started`, `turn.completed`, `item.completed` events, usage tracking.
  - Value: Learn the event flow for debugging and observability.

## Tool Streaming

**Note:** Codex CLI executes tools autonomously, so the provider sets `providerExecuted: true` on all tool calls. This means the AI SDK will not attempt to execute tools—it simply receives the results from Codex CLI.

**⚠️ Streaming Limitation:** Real-time output streaming (`output-delta` events) is not yet available. Tool outputs are delivered in the final `tool-result` event via the `aggregatedOutput` field. The provider correctly implements the AI SDK tool streaming API, but incremental stdout/stderr streaming will require additional support in Codex CLI's event format.

- **streaming-tool-calls.mjs:** Basic tool streaming
  - Purpose: Demonstrate tool streaming API with Codex CLI tool execution.
  - Demonstrates: `tool-input-start`, `tool-input-delta`, `tool-input-end`, `tool-call`, `tool-result` events for exec commands.
  - Value: See how tool invocation and results flow through the AI SDK streaming interface. Monitor what tools Codex CLI executes in real time.
  - Note: Tool outputs appear in final result, not as streaming deltas (see limitation above).

- **streaming-multiple-tools.mjs:** Multiple sequential tool calls
  - Purpose: Show complex multi-tool workflows with result tracking.
  - Demonstrates: Sequential tool execution, abbreviated output display, tool call numbering.
  - Value: Build UIs that track progress across multiple tool invocations. Great for debugging complex agent workflows.
  - Note: Shows tool inputs immediately and outputs when completed (aggregated, not streaming).

## Suggested Run Order

1. `basic-usage.mjs` → `streaming.mjs` → `conversation-history.mjs`
2. `custom-config.mjs` → `permissions-and-sandbox.mjs` → `advanced-settings.mjs` → `provider-options.mjs` (v0.4.0 features)
3. `logging-default.mjs` → `logging-verbose.mjs` → `logging-custom-logger.mjs` → `logging-disabled.mjs` (logging)
4. `generate-object-basic.mjs` → `generate-object-nested.mjs` → `generate-object-constraints.mjs` → `generate-object-advanced.mjs` → `generate-object-native-schema.mjs`
5. `experimental-json-events.mjs` (v0.2.0 event format)
6. `streaming-tool-calls.mjs` → `streaming-multiple-tools.mjs` (tool streaming)
7. `long-running-tasks.mjs` → `error-handling.mjs` → `limitations.mjs` → `check-cli.mjs`

## Troubleshooting

- Not getting output? Run `node examples/check-cli.mjs`.
- Auth failures? Run `codex login` or set `OPENAI_API_KEY`.
- PATH issues? Keep `allowNpx: true` or install `@openai/codex` globally.
- Streaming not incremental? The `--experimental-json` format (introduced Sept 25, 2025) doesn't yet support streaming deltas—you'll receive the full response in a single chunk. This is expected behavior until OpenAI adds delta event support. The streaming API pattern remains correct for future compatibility.</doc></tutorials><src><doc title="Codex Cli Language Model" desc="docs page.">import { spawn } from 'node:child_process';
import { randomUUID } from 'node:crypto';
import { createRequire } from 'node:module';
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import type { ReadableStreamDefaultController } from 'node:stream/web';
import { z } from 'zod';
import type {
  LanguageModelV2,
  LanguageModelV2CallWarning,
  LanguageModelV2FinishReason,
  LanguageModelV2StreamPart,
  LanguageModelV2Usage,
  LanguageModelV2Content,
} from '@ai-sdk/provider';
import { NoSuchModelError } from '@ai-sdk/provider';
import { generateId, parseProviderOptions } from '@ai-sdk/provider-utils';
import { getLogger, createVerboseLogger } from './logger.js';
import type {
  CodexCliProviderOptions,
  CodexCliSettings,
  Logger,
  McpServerConfig,
  McpServerStdio,
  McpServerHttp,
} from './types.js';
import { mcpServersSchema, validateModelId } from './validation.js';
import { mapMessagesToPrompt } from './message-mapper.js';
import { createAPICallError, createAuthenticationError } from './errors.js';

export interface CodexLanguageModelOptions {
  id: string; // model id for Codex (-m)
  settings?: CodexCliSettings;
}

// Experimental JSON event format from --experimental-json
interface ExperimentalJsonEvent {
  type?: string;
  session_id?: string;
  thread_id?: string;
  usage?: {
    input_tokens?: number;
    output_tokens?: number;
    cached_input_tokens?: number;
  };
  item?: {
    id?: string;
    item_type?: string; // Flattened from ConversationItemDetails
    text?: string; // For assistant_message and reasoning items
    [k: string]: unknown;
  };
  message?: string; // For error events
  error?: {
    message?: string;
    [k: string]: unknown;
  };
  [k: string]: unknown;
}

type ExperimentalJsonItem = NonNullable<ExperimentalJsonEvent['item']>;

interface ActiveToolItem {
  toolCallId: string;
  toolName: string;
  inputPayload?: unknown;
  hasEmittedCall: boolean;
}

const codexCliProviderOptionsSchema: z.ZodType<CodexCliProviderOptions> = z
  .object({
    reasoningEffort: z.enum(['minimal', 'low', 'medium', 'high', 'xhigh']).optional(),
    reasoningSummary: z.enum(['auto', 'detailed']).optional(),
    reasoningSummaryFormat: z.enum(['none', 'experimental']).optional(),
    textVerbosity: z.enum(['low', 'medium', 'high']).optional(),
    addDirs: z.array(z.string().min(1)).optional(),
    configOverrides: z
      .record(
        z.string(),
        z.union([
          z.string(),
          z.number(),
          z.boolean(),
          z.object({}).passthrough(),
          z.array(z.any()),
        ]),
      )
      .optional(),
    mcpServers: mcpServersSchema.optional(),
    rmcpClient: z.boolean().optional(),
  })
  .strict();

function resolveCodexPath(
  explicitPath?: string,
  allowNpx?: boolean,
): { cmd: string; args: string[] } {
  if (explicitPath) return { cmd: 'node', args: [explicitPath] };

  try {
    const req = createRequire(import.meta.url);
    const pkgPath = req.resolve('@openai/codex/package.json');
    const root = pkgPath.replace(/package\.json$/, '');
    return { cmd: 'node', args: [root + 'bin/codex.js'] };
  } catch {
    // Fallback to PATH or npx
    if (allowNpx) return { cmd: 'npx', args: ['-y', '@openai/codex'] };
    return { cmd: 'codex', args: [] };
  }
}

export class CodexCliLanguageModel implements LanguageModelV2 {
  readonly specificationVersion = 'v2' as const;
  readonly provider = 'codex-cli';
  readonly defaultObjectGenerationMode = 'json' as const;
  readonly supportsImageUrls = false;
  readonly supportedUrls = {};
  readonly supportsStructuredOutputs = true;

  readonly modelId: string;
  readonly settings: CodexCliSettings;

  private logger: Logger;
  private sessionId?: string;

  constructor(options: CodexLanguageModelOptions) {
    this.modelId = options.id;
    this.settings = options.settings ?? {};
    const baseLogger = getLogger(this.settings.logger);
    this.logger = createVerboseLogger(baseLogger, this.settings.verbose ?? false);
    if (!this.modelId || this.modelId.trim() === '') {
      throw new NoSuchModelError({ modelId: this.modelId, modelType: 'languageModel' });
    }
    const warn = validateModelId(this.modelId);
    if (warn) this.logger.warn(`Codex CLI model: ${warn}`);
  }

  private mergeSettings(providerOptions?: CodexCliProviderOptions): CodexCliSettings {
    if (!providerOptions) return this.settings;

    const mergedConfigOverrides =
      providerOptions.configOverrides || this.settings.configOverrides
        ? {
            ...(this.settings.configOverrides ?? {}),
            ...(providerOptions.configOverrides ?? {}),
          }
        : undefined;

    const mergedAddDirs =
      providerOptions.addDirs || this.settings.addDirs
        ? [...(this.settings.addDirs ?? []), ...(providerOptions.addDirs ?? [])]
        : undefined;

    const mergedMcpServers = this.mergeMcpServers(
      this.settings.mcpServers,
      providerOptions.mcpServers,
    );

    return {
      ...this.settings,
      reasoningEffort: providerOptions.reasoningEffort ?? this.settings.reasoningEffort,
      reasoningSummary: providerOptions.reasoningSummary ?? this.settings.reasoningSummary,
      reasoningSummaryFormat:
        providerOptions.reasoningSummaryFormat ?? this.settings.reasoningSummaryFormat,
      modelVerbosity: providerOptions.textVerbosity ?? this.settings.modelVerbosity,
      configOverrides: mergedConfigOverrides,
      addDirs: mergedAddDirs,
      mcpServers: mergedMcpServers,
      rmcpClient: providerOptions.rmcpClient ?? this.settings.rmcpClient,
    };
  }

  private mergeMcpServers(
    base?: Record<string, McpServerConfig>,
    override?: Record<string, McpServerConfig>,
  ): Record<string, McpServerConfig> | undefined {
    if (!base) return override;
    if (!override) return base;

    const merged: Record<string, McpServerConfig> = { ...base };
    for (const [name, incoming] of Object.entries(override)) {
      const existing = base[name];
      merged[name] = this.mergeSingleMcpServer(existing, incoming);
    }
    return merged;
  }

  private mergeSingleMcpServer(
    existing: McpServerConfig | undefined,
    incoming: McpServerConfig,
  ): McpServerConfig {
    if (!existing || existing.transport !== incoming.transport) {
      return { ...incoming };
    }

    if (incoming.transport === 'stdio') {
      const baseStdio = existing as McpServerStdio;
      const result: McpServerConfig = {
        transport: 'stdio',
        command: incoming.command,
        args: incoming.args ?? baseStdio.args,
        env: this.mergeStringRecord(baseStdio.env, incoming.env),
        cwd: incoming.cwd ?? baseStdio.cwd,
        enabled: incoming.enabled ?? existing.enabled,
        startupTimeoutSec: incoming.startupTimeoutSec ?? existing.startupTimeoutSec,
        toolTimeoutSec: incoming.toolTimeoutSec ?? existing.toolTimeoutSec,
        enabledTools: incoming.enabledTools ?? existing.enabledTools,
        disabledTools: incoming.disabledTools ?? existing.disabledTools,
      } as McpServerConfig;
      return result;
    }

    const baseHttp = existing as McpServerHttp;
    // Treat auth fields as a bundle: if incoming defines either, override both.
    const hasIncomingAuth =
      incoming.bearerToken !== undefined || incoming.bearerTokenEnvVar !== undefined;
    const bearerToken = hasIncomingAuth ? incoming.bearerToken : baseHttp.bearerToken;
    const bearerTokenEnvVar = hasIncomingAuth
      ? incoming.bearerTokenEnvVar
      : baseHttp.bearerTokenEnvVar;

    const result: McpServerConfig = {
      transport: 'http',
      url: incoming.url,
      bearerToken,
      bearerTokenEnvVar,
      httpHeaders: this.mergeStringRecord(baseHttp.httpHeaders, incoming.httpHeaders),
      envHttpHeaders: this.mergeStringRecord(baseHttp.envHttpHeaders, incoming.envHttpHeaders),
      enabled: incoming.enabled ?? existing.enabled,
      startupTimeoutSec: incoming.startupTimeoutSec ?? existing.startupTimeoutSec,
      toolTimeoutSec: incoming.toolTimeoutSec ?? existing.toolTimeoutSec,
      enabledTools: incoming.enabledTools ?? existing.enabledTools,
      disabledTools: incoming.disabledTools ?? existing.disabledTools,
    };

    return result;
  }

  private mergeStringRecord(
    base?: Record<string, string>,
    override?: Record<string, string>,
  ): Record<string, string> | undefined {
    if (override !== undefined) {
      if (Object.keys(override).length === 0) return {};
      return { ...(base ?? {}), ...override };
    }
    if (base) return { ...base };
    return undefined;
  }

  // Codex JSONL items use `type` for the item discriminator, but some
  // earlier fixtures (and defensive parsing) might still surface `item_type`.
  // This helper returns whichever is present.
  private getItemType(item?: ExperimentalJsonItem): string | undefined {
    if (!item) return undefined;
    const data = item as Record<string, unknown>;
    const legacy = typeof data.item_type === 'string' ? (data.item_type as string) : undefined;
    const current = typeof data.type === 'string' ? (data.type as string) : undefined;
    return legacy ?? current;
  }

  private buildArgs(
    promptText: string,
    responseFormat?: { type: 'json'; schema: unknown },
    settings: CodexCliSettings = this.settings,
  ): {
    cmd: string;
    args: string[];
    env: NodeJS.ProcessEnv;
    cwd?: string;
    lastMessagePath?: string;
    lastMessageIsTemp?: boolean;
    schemaPath?: string;
  } {
    const base = resolveCodexPath(settings.codexPath, settings.allowNpx);
    const args: string[] = [...base.args, 'exec', '--experimental-json'];

    // Approval/sandbox (exec subcommand does not accept -a/-s directly; use -c overrides)
    if (settings.fullAuto) {
      args.push('--full-auto');
    } else if (settings.dangerouslyBypassApprovalsAndSandbox) {
      args.push('--dangerously-bypass-approvals-and-sandbox');
    } else {
      const approval = settings.approvalMode ?? 'on-failure';
      args.push('-c', `approval_policy=${approval}`);
      const sandbox = settings.sandboxMode ?? 'workspace-write';
      args.push('-c', `sandbox_mode=${sandbox}`);
    }

    if (settings.skipGitRepoCheck !== false) {
      args.push('--skip-git-repo-check');
    }

    // Reasoning & verbosity
    if (settings.reasoningEffort) {
      args.push('-c', `model_reasoning_effort=${settings.reasoningEffort}`);
    }
    if (settings.reasoningSummary) {
      args.push('-c', `model_reasoning_summary=${settings.reasoningSummary}`);
    }
    if (settings.reasoningSummaryFormat) {
      args.push('-c', `model_reasoning_summary_format=${settings.reasoningSummaryFormat}`);
    }
    if (settings.modelVerbosity) {
      args.push('-c', `model_verbosity=${settings.modelVerbosity}`);
    }

    // Advanced Codex features
    if (settings.includePlanTool) {
      args.push('--include-plan-tool');
    }
    if (settings.profile) {
      args.push('--profile', settings.profile);
    }
    if (settings.oss) {
      args.push('--oss');
    }
    if (settings.webSearch) {
      args.push('-c', 'tools.web_search=true');
    }

    // MCP configuration
    this.applyMcpSettings(args, settings);

    // Color handling
    if (settings.color) {
      args.push('--color', settings.color);
    }

    if (this.modelId) {
      args.push('-m', this.modelId);
    }

    if (settings.addDirs?.length) {
      for (const dir of settings.addDirs) {
        if (typeof dir === 'string' && dir.trim().length > 0) {
          args.push('--add-dir', dir);
        }
      }
    }

    // Generic config overrides (-c key=value)
    if (settings.configOverrides) {
      for (const [key, value] of Object.entries(settings.configOverrides)) {
        this.addConfigOverride(args, key, value);
      }
    }

    // Handle JSON schema if provided
    let schemaPath: string | undefined;
    if (responseFormat?.type === 'json' && responseFormat.schema) {
      const schema = typeof responseFormat.schema === 'object' ? responseFormat.schema : {};
      const sanitizedSchema = this.sanitizeJsonSchema(schema) as Record<string, unknown>;

      // Only write schema if it has properties (not empty schema like z.any())
      const hasProperties = Object.keys(sanitizedSchema).length > 0;
      if (hasProperties) {
        const dir = mkdtempSync(join(tmpdir(), 'codex-schema-'));
        schemaPath = join(dir, 'schema.json');

        // OpenAI strict mode requires additionalProperties=false for structured schemas
        const schemaWithAdditional = {
          ...sanitizedSchema,
          additionalProperties: false,
        };

        writeFileSync(schemaPath, JSON.stringify(schemaWithAdditional, null, 2));
        args.push('--output-schema', schemaPath);
      }
    }

    // Prompt as positional arg (avoid stdin for reliability)
    args.push(promptText);

    const env: NodeJS.ProcessEnv = {
      ...process.env,
      ...(settings.env || {}),
      RUST_LOG: process.env.RUST_LOG || 'error',
    };

    // Configure output-last-message
    let lastMessagePath: string | undefined = settings.outputLastMessageFile;
    let lastMessageIsTemp = false;
    if (!lastMessagePath) {
      // create a temp folder for this run
      const dir = mkdtempSync(join(tmpdir(), 'codex-cli-'));
      lastMessagePath = join(dir, 'last-message.txt');
      lastMessageIsTemp = true;
    }
    args.push('--output-last-message', lastMessagePath);

    return {
      cmd: base.cmd,
      args,
      env,
      cwd: settings.cwd,
      lastMessagePath,
      lastMessageIsTemp,
      schemaPath,
    };
  }

  private applyMcpSettings(args: string[], settings: CodexCliSettings): void {
    if (settings.rmcpClient) {
      this.addConfigOverride(args, 'features.rmcp_client', true);
    }

    if (!settings.mcpServers) return;

    for (const [rawName, server] of Object.entries(settings.mcpServers)) {
      const name = rawName.trim();
      if (!name) continue;
      const prefix = `mcp_servers.${name}`;

      if (server.enabled !== undefined) {
        this.addConfigOverride(args, `${prefix}.enabled`, server.enabled);
      }
      if (server.startupTimeoutSec !== undefined) {
        this.addConfigOverride(args, `${prefix}.startup_timeout_sec`, server.startupTimeoutSec);
      }
      if (server.toolTimeoutSec !== undefined) {
        this.addConfigOverride(args, `${prefix}.tool_timeout_sec`, server.toolTimeoutSec);
      }
      if (server.enabledTools !== undefined) {
        this.addConfigOverride(args, `${prefix}.enabled_tools`, server.enabledTools);
      }
      if (server.disabledTools !== undefined) {
        this.addConfigOverride(args, `${prefix}.disabled_tools`, server.disabledTools);
      }

      if (server.transport === 'stdio') {
        this.addConfigOverride(args, `${prefix}.command`, server.command);
        if (server.args !== undefined) this.addConfigOverride(args, `${prefix}.args`, server.args);
        if (server.env !== undefined) this.addConfigOverride(args, `${prefix}.env`, server.env);
        if (server.cwd) this.addConfigOverride(args, `${prefix}.cwd`, server.cwd);
      } else {
        this.addConfigOverride(args, `${prefix}.url`, server.url);
        if (server.bearerToken !== undefined)
          this.addConfigOverride(args, `${prefix}.bearer_token`, server.bearerToken);
        if (server.bearerTokenEnvVar)
          this.addConfigOverride(args, `${prefix}.bearer_token_env_var`, server.bearerTokenEnvVar);
        if (server.httpHeaders !== undefined)
          this.addConfigOverride(args, `${prefix}.http_headers`, server.httpHeaders);
        if (server.envHttpHeaders !== undefined)
          this.addConfigOverride(args, `${prefix}.env_http_headers`, server.envHttpHeaders);
      }
    }
  }

  private addConfigOverride(
    args: string[],
    key: string,
    value: string | number | boolean | object,
  ): void {
    if (this.isPlainObject(value)) {
      const entries = Object.entries(value);
      if (entries.length === 0) {
        args.push('-c', `${key}={}`);
        return;
      }
      for (const [childKey, childValue] of entries) {
        this.addConfigOverride(
          args,
          `${key}.${childKey}`,
          childValue as string | number | boolean | object,
        );
      }
      return;
    }
    const serialized = this.serializeConfigValue(value);
    args.push('-c', `${key}=${serialized}`);
  }

  /**
   * Serialize a config override value into a CLI-safe string.
   */
  private serializeConfigValue(value: string | number | boolean | object): string {
    if (typeof value === 'string') return value;
    if (typeof value === 'number' || typeof value === 'boolean') return String(value);
    if (Array.isArray(value)) {
      try {
        return JSON.stringify(value);
      } catch {
        return String(value);
      }
    }
    if (value && typeof value === 'object') {
      // Remaining plain objects are flattened earlier; fallback to JSON.
      try {
        return JSON.stringify(value);
      } catch {
        return String(value);
      }
    }
    return String(value);
  }

  private isPlainObject(value: unknown): value is Record<string, unknown> {
    return (
      typeof value === 'object' &&
      value !== null &&
      !Array.isArray(value) &&
      Object.prototype.toString.call(value) === '[object Object]'
    );
  }

  private sanitizeJsonSchema(value: unknown): unknown {
    // Remove fields that OpenAI strict mode doesn't support
    // Based on codex-rs/core/src/openai_tools.rs sanitize_json_schema
    if (typeof value !== 'object' || value === null) {
      return value;
    }

    if (Array.isArray(value)) {
      return value.map((item) => this.sanitizeJsonSchema(item));
    }

    const obj = value as Record<string, unknown>;
    const result: Record<string, unknown> = {};

    for (const [key, val] of Object.entries(obj)) {
      // Special handling for 'properties' - preserve all property names, sanitize their schemas
      if (key === 'properties' && typeof val === 'object' && val !== null && !Array.isArray(val)) {
        const props = val as Record<string, unknown>;
        const sanitizedProps: Record<string, unknown> = {};
        for (const [propName, propSchema] of Object.entries(props)) {
          // Keep property name, sanitize its schema
          sanitizedProps[propName] = this.sanitizeJsonSchema(propSchema);
        }
        result[key] = sanitizedProps;
        continue;
      }

      // Remove unsupported metadata fields
      if (
        key === '$schema' ||
        key === '$id' ||
        key === '$ref' ||
        key === '$defs' ||
        key === 'definitions' ||
        key === 'title' ||
        key === 'examples' ||
        key === 'default' ||
        key === 'format' || // OpenAI strict mode doesn't support format
        key === 'pattern' // OpenAI strict mode doesn't support pattern
      ) {
        continue;
      }

      // Recursively sanitize nested objects and arrays
      result[key] = this.sanitizeJsonSchema(val);
    }

    return result;
  }

  private mapWarnings(
    options: Parameters<LanguageModelV2['doGenerate']>[0],
  ): LanguageModelV2CallWarning[] {
    const unsupported: LanguageModelV2CallWarning[] = [];
    const add = (setting: unknown, name: string) => {
      if (setting !== undefined)
        unsupported.push({
          type: 'unsupported-setting',
          setting: name,
          details: `Codex CLI does not support ${name}; it will be ignored.`,
        } as LanguageModelV2CallWarning);
    };
    add(options.temperature, 'temperature');
    add(options.topP, 'topP');
    add(options.topK, 'topK');
    add(options.presencePenalty, 'presencePenalty');
    add(options.frequencyPenalty, 'frequencyPenalty');
    add(options.stopSequences?.length ? options.stopSequences : undefined, 'stopSequences');
    add((options as { seed?: unknown }).seed, 'seed');
    return unsupported;
  }

  private parseExperimentalJsonEvent(line: string): ExperimentalJsonEvent | undefined {
    try {
      return JSON.parse(line) as ExperimentalJsonEvent;
    } catch {
      return undefined;
    }
  }

  private extractUsage(evt: ExperimentalJsonEvent): LanguageModelV2Usage | undefined {
    const reported = evt.usage;
    if (!reported) return undefined;
    const inputTokens = reported.input_tokens ?? 0;
    const outputTokens = reported.output_tokens ?? 0;
    const cachedInputTokens = reported.cached_input_tokens ?? 0;
    return {
      inputTokens,
      outputTokens,
      // totalTokens should not double-count cached tokens; track cached separately
      totalTokens: inputTokens + outputTokens,
      cachedInputTokens,
    };
  }

  private getToolName(item?: ExperimentalJsonItem): string | undefined {
    if (!item) return undefined;
    const itemType = this.getItemType(item);
    switch (itemType) {
      case 'command_execution':
        return 'exec';
      case 'file_change':
        return 'patch';
      case 'mcp_tool_call': {
        const tool = (item as Record<string, unknown>).tool;
        if (typeof tool === 'string' && tool.length > 0) return tool;
        return 'mcp_tool';
      }
      case 'web_search':
        return 'web_search';
      default:
        return undefined;
    }
  }

  private buildToolInputPayload(item?: ExperimentalJsonItem): unknown {
    if (!item) return undefined;
    const data = item as Record<string, unknown>;
    switch (this.getItemType(item)) {
      case 'command_execution': {
        const payload: Record<string, unknown> = {};
        if (typeof data.command === 'string') payload.command = data.command;
        if (typeof data.status === 'string') payload.status = data.status;
        if (typeof data.cwd === 'string') payload.cwd = data.cwd;
        return Object.keys(payload).length ? payload : undefined;
      }
      case 'file_change': {
        const payload: Record<string, unknown> = {};
        if (Array.isArray(data.changes)) payload.changes = data.changes;
        if (typeof data.status === 'string') payload.status = data.status;
        return Object.keys(payload).length ? payload : undefined;
      }
      case 'mcp_tool_call': {
        const payload: Record<string, unknown> = {};
        if (typeof data.server === 'string') payload.server = data.server;
        if (typeof data.tool === 'string') payload.tool = data.tool;
        if (typeof data.status === 'string') payload.status = data.status;
        // Include arguments so consumers can see what parameters were passed
        if (data.arguments !== undefined) payload.arguments = data.arguments;
        return Object.keys(payload).length ? payload : undefined;
      }
      case 'web_search': {
        const payload: Record<string, unknown> = {};
        if (typeof data.query === 'string') payload.query = data.query;
        return Object.keys(payload).length ? payload : undefined;
      }
      default:
        return undefined;
    }
  }

  private buildToolResultPayload(item?: ExperimentalJsonItem): {
    result: unknown;
    metadata?: Record<string, string>;
  } {
    if (!item) return { result: {} };
    const data = item as Record<string, unknown>;
    const metadata: Record<string, string> = {};
    const itemType = this.getItemType(item);
    if (typeof itemType === 'string') metadata.itemType = itemType;
    if (typeof item.id === 'string') metadata.itemId = item.id;
    if (typeof data.status === 'string') metadata.status = data.status;

    const buildResult = (result: Record<string, unknown>) => ({
      result,
      metadata: Object.keys(metadata).length ? metadata : undefined,
    });

    switch (itemType) {
      case 'command_execution': {
        const result: Record<string, unknown> = {};
        if (typeof data.command === 'string') result.command = data.command;
        if (typeof data.aggregated_output === 'string')
          result.aggregatedOutput = data.aggregated_output;
        if (typeof data.exit_code === 'number') result.exitCode = data.exit_code;
        if (typeof data.status === 'string') result.status = data.status;
        return buildResult(result);
      }
      case 'file_change': {
        const result: Record<string, unknown> = {};
        if (Array.isArray(data.changes)) result.changes = data.changes;
        if (typeof data.status === 'string') result.status = data.status;
        return buildResult(result);
      }
      case 'mcp_tool_call': {
        const result: Record<string, unknown> = {};
        if (typeof data.server === 'string') {
          result.server = data.server;
          metadata.server = data.server;
        }
        if (typeof data.tool === 'string') result.tool = data.tool;
        if (typeof data.status === 'string') result.status = data.status;
        // Include result payload so consumers can see what the tool returned
        if (data.result !== undefined) result.result = data.result;
        // Include error details if present
        if (data.error !== undefined) result.error = data.error;
        return buildResult(result);
      }
      case 'web_search': {
        const result: Record<string, unknown> = {};
        if (typeof data.query === 'string') result.query = data.query;
        if (typeof data.status === 'string') result.status = data.status;
        return buildResult(result);
      }
      default: {
        const result = { ...data };
        return buildResult(result);
      }
    }
  }

  private safeStringify(value: unknown): string {
    if (value === undefined) return '';
    if (typeof value === 'string') return value;
    try {
      return JSON.stringify(value);
    } catch {
      return '';
    }
  }

  private emitToolInvocation(
    controller: ReadableStreamDefaultController<LanguageModelV2StreamPart>,
    toolCallId: string,
    toolName: string,
    inputPayload: unknown,
  ): void {
    const inputString = this.safeStringify(inputPayload);
    controller.enqueue({ type: 'tool-input-start', id: toolCallId, toolName });
    if (inputString) {
      controller.enqueue({ type: 'tool-input-delta', id: toolCallId, delta: inputString });
    }
    controller.enqueue({ type: 'tool-input-end', id: toolCallId });
    controller.enqueue({
      type: 'tool-call',
      toolCallId,
      toolName,
      input: inputString,
      providerExecuted: true,
    });
  }

  private emitToolResult(
    controller: ReadableStreamDefaultController<LanguageModelV2StreamPart>,
    toolCallId: string,
    toolName: string,
    item: ExperimentalJsonItem,
    resultPayload: unknown,
    metadata?: Record<string, string>,
  ): void {
    const providerMetadataEntries: Record<string, string> = {
      ...(metadata ?? {}),
    };
    const itemType = this.getItemType(item);
    if (itemType && providerMetadataEntries.itemType === undefined) {
      providerMetadataEntries.itemType = itemType;
    }
    if (item.id && providerMetadataEntries.itemId === undefined) {
      providerMetadataEntries.itemId = item.id;
    }

    // Determine error status for command executions
    let isError: boolean | undefined;
    if (itemType === 'command_execution') {
      const data = item as Record<string, unknown>;
      const exitCode = typeof data.exit_code === 'number' ? (data.exit_code as number) : undefined;
      const status = typeof data.status === 'string' ? (data.status as string) : undefined;
      if ((exitCode !== undefined && exitCode !== 0) || status === 'failed') {
        isError = true;
      }
    }

    controller.enqueue({
      type: 'tool-result',
      toolCallId,
      toolName,
      result: resultPayload ?? {},
      ...(isError ? { isError: true } : {}),
      ...(Object.keys(providerMetadataEntries).length
        ? { providerMetadata: { 'codex-cli': providerMetadataEntries } }
        : {}),
    });
  }

  private handleSpawnError(err: unknown, promptExcerpt: string) {
    const e =
      err && typeof err === 'object'
        ? (err as {
            message?: unknown;
            code?: unknown;
            exitCode?: unknown;
            stderr?: unknown;
          })
        : undefined;
    const message = String((e?.message ?? err) || 'Failed to run Codex CLI');
    // crude auth detection
    if (/login|auth|unauthorized|not\s+logged/i.test(message)) {
      throw createAuthenticationError(message);
    }
    throw createAPICallError({
      message,
      code: typeof e?.code === 'string' ? e.code : undefined,
      exitCode: typeof e?.exitCode === 'number' ? e.exitCode : undefined,
      stderr: typeof e?.stderr === 'string' ? e.stderr : undefined,
      promptExcerpt,
    });
  }

  async doGenerate(
    options: Parameters<LanguageModelV2['doGenerate']>[0],
  ): Promise<Awaited<ReturnType<LanguageModelV2['doGenerate']>>> {
    this.logger.debug(`[codex-cli] Starting doGenerate request with model: ${this.modelId}`);

    const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
    const promptExcerpt = promptText.slice(0, 200);
    const warnings = [
      ...this.mapWarnings(options),
      ...(mappingWarnings?.map((m) => ({ type: 'other', message: m })) || []),
    ] as LanguageModelV2CallWarning[];

    this.logger.debug(
      `[codex-cli] Converted ${options.prompt.length} messages, response format: ${options.responseFormat?.type ?? 'none'}`,
    );

    const providerOptions = await parseProviderOptions<CodexCliProviderOptions>({
      provider: this.provider,
      providerOptions: options.providerOptions,
      schema: codexCliProviderOptionsSchema,
    });
    const effectiveSettings = this.mergeSettings(providerOptions);

    const responseFormat =
      options.responseFormat?.type === 'json'
        ? { type: 'json' as const, schema: options.responseFormat.schema }
        : undefined;
    const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath } = this.buildArgs(
      promptText,
      responseFormat,
      effectiveSettings,
    );

    this.logger.debug(
      `[codex-cli] Executing Codex CLI: ${cmd} with ${args.length} arguments, cwd: ${cwd ?? 'default'}`,
    );

    let text = '';
    const usage: LanguageModelV2Usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
    const finishReason: LanguageModelV2FinishReason = 'stop';
    const startTime = Date.now();

    const child = spawn(cmd, args, { env, cwd, stdio: ['ignore', 'pipe', 'pipe'] });

    // Abort support
    let onAbort: (() => void) | undefined;
    if (options.abortSignal) {
      if (options.abortSignal.aborted) {
        child.kill('SIGTERM');
        throw options.abortSignal.reason ?? new Error('Request aborted');
      }
      onAbort = () => child.kill('SIGTERM');
      options.abortSignal.addEventListener('abort', onAbort, { once: true });
    }

    try {
      await new Promise<void>((resolve, reject) => {
        let stderr = '';
        let turnFailureMessage: string | undefined;
        child.stderr.on('data', (d) => (stderr += String(d)));
        child.stdout.setEncoding('utf8');
        child.stdout.on('data', (chunk: string) => {
          const lines = chunk.split(/\r?\n/).filter(Boolean);
          for (const line of lines) {
            const event = this.parseExperimentalJsonEvent(line);
            if (!event) continue;

            this.logger.debug(`[codex-cli] Received event type: ${event.type ?? 'unknown'}`);

            if (event.type === 'thread.started' && typeof event.thread_id === 'string') {
              this.sessionId = event.thread_id;
              this.logger.debug(`[codex-cli] Session started: ${this.sessionId}`);
            }
            if (event.type === 'session.created' && typeof event.session_id === 'string') {
              // Backwards compatibility in case older events appear
              this.sessionId = event.session_id;
              this.logger.debug(`[codex-cli] Session created: ${this.sessionId}`);
            }

            if (event.type === 'turn.completed') {
              const usageEvent = this.extractUsage(event);
              if (usageEvent) {
                usage.inputTokens = usageEvent.inputTokens;
                usage.outputTokens = usageEvent.outputTokens;
                usage.totalTokens = usageEvent.totalTokens;
              }
            }

            if (
              event.type === 'item.completed' &&
              this.getItemType(event.item) === 'assistant_message' &&
              typeof event.item?.text === 'string'
            ) {
              text = event.item.text;
            }

            if (event.type === 'turn.failed') {
              const errorText =
                (event.error && typeof event.error.message === 'string' && event.error.message) ||
                (typeof event.message === 'string' ? event.message : undefined);
              turnFailureMessage = errorText ?? turnFailureMessage ?? 'Codex turn failed';
              this.logger.error(`[codex-cli] Turn failed: ${turnFailureMessage}`);
            }

            if (event.type === 'error') {
              const errorText = typeof event.message === 'string' ? event.message : undefined;
              turnFailureMessage = errorText ?? turnFailureMessage ?? 'Codex error';
              this.logger.error(`[codex-cli] Error event: ${turnFailureMessage}`);
            }
          }
        });
        child.on('error', (e) => {
          this.logger.error(`[codex-cli] Spawn error: ${String(e)}`);
          reject(this.handleSpawnError(e, promptExcerpt));
        });
        child.on('close', (code) => {
          const duration = Date.now() - startTime;
          if (code === 0) {
            if (turnFailureMessage) {
              reject(
                createAPICallError({
                  message: turnFailureMessage,
                  stderr,
                  promptExcerpt,
                }),
              );
              return;
            }
            this.logger.info(
              `[codex-cli] Request completed - Session: ${this.sessionId ?? 'N/A'}, Duration: ${duration}ms, Tokens: ${usage.totalTokens}`,
            );
            this.logger.debug(
              `[codex-cli] Token usage - Input: ${usage.inputTokens}, Output: ${usage.outputTokens}, Total: ${usage.totalTokens}`,
            );
            resolve();
          } else {
            this.logger.error(`[codex-cli] Process exited with code ${code} after ${duration}ms`);
            reject(
              createAPICallError({
                message: `Codex CLI exited with code ${code}`,
                exitCode: code ?? undefined,
                stderr,
                promptExcerpt,
              }),
            );
          }
        });
      });
    } finally {
      if (options.abortSignal && onAbort) options.abortSignal.removeEventListener('abort', onAbort);
      // Clean up temp schema file
      if (schemaPath) {
        try {
          const schemaDir = dirname(schemaPath);
          rmSync(schemaDir, { recursive: true, force: true });
        } catch {}
      }
    }

    // Fallback: read last message file if needed
    if (!text && lastMessagePath) {
      try {
        const fileText = readFileSync(lastMessagePath, 'utf8');
        if (fileText && typeof fileText === 'string') {
          text = fileText.trim();
        }
      } catch {}
      // best-effort cleanup for temp paths only
      if (lastMessageIsTemp) {
        try {
          rmSync(lastMessagePath, { force: true });
        } catch {}
      }
    }

    // No JSON extraction needed - native schema guarantees valid JSON

    const content: LanguageModelV2Content[] = [{ type: 'text', text }];
    return {
      content,
      usage,
      finishReason,
      warnings,
      response: { id: generateId(), timestamp: new Date(), modelId: this.modelId },
      request: { body: promptText },
      providerMetadata: {
        'codex-cli': { ...(this.sessionId ? { sessionId: this.sessionId } : {}) },
      },
    };
  }

  async doStream(
    options: Parameters<LanguageModelV2['doStream']>[0],
  ): Promise<Awaited<ReturnType<LanguageModelV2['doStream']>>> {
    this.logger.debug(`[codex-cli] Starting doStream request with model: ${this.modelId}`);

    const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
    const promptExcerpt = promptText.slice(0, 200);
    const warnings = [
      ...this.mapWarnings(options),
      ...(mappingWarnings?.map((m) => ({ type: 'other', message: m })) || []),
    ] as LanguageModelV2CallWarning[];

    this.logger.debug(
      `[codex-cli] Converted ${options.prompt.length} messages for streaming, response format: ${options.responseFormat?.type ?? 'none'}`,
    );

    const providerOptions = await parseProviderOptions<CodexCliProviderOptions>({
      provider: this.provider,
      providerOptions: options.providerOptions,
      schema: codexCliProviderOptionsSchema,
    });
    const effectiveSettings = this.mergeSettings(providerOptions);

    const responseFormat =
      options.responseFormat?.type === 'json'
        ? { type: 'json' as const, schema: options.responseFormat.schema }
        : undefined;
    const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath } = this.buildArgs(
      promptText,
      responseFormat,
      effectiveSettings,
    );

    this.logger.debug(
      `[codex-cli] Executing Codex CLI for streaming: ${cmd} with ${args.length} arguments`,
    );

    const stream = new ReadableStream<LanguageModelV2StreamPart>({
      start: (controller) => {
        const startTime = Date.now();
        const child = spawn(cmd, args, { env, cwd, stdio: ['ignore', 'pipe', 'pipe'] });

        // Emit stream-start
        controller.enqueue({ type: 'stream-start', warnings });

        let stderr = '';
        let accumulatedText = '';
        const activeTools = new Map<string, ActiveToolItem>();
        let responseMetadataSent = false;
        let lastUsage: LanguageModelV2Usage | undefined;
        let turnFailureMessage: string | undefined;

        const sendMetadata = (meta: Record<string, string> = {}) => {
          controller.enqueue({
            type: 'response-metadata',
            id: randomUUID(),
            timestamp: new Date(),
            modelId: this.modelId,
            ...(Object.keys(meta).length ? { providerMetadata: { 'codex-cli': meta } } : {}),
          });
        };

        const handleItemEvent = (event: ExperimentalJsonEvent) => {
          const item = event.item;
          if (!item) return;

          if (
            event.type === 'item.completed' &&
            this.getItemType(item) === 'assistant_message' &&
            typeof item.text === 'string'
          ) {
            accumulatedText = item.text;
            this.logger.debug(
              `[codex-cli] Received assistant message, length: ${item.text.length}`,
            );
            return;
          }

          const toolName = this.getToolName(item);
          if (!toolName) {
            return;
          }

          this.logger.debug(
            `[codex-cli] Tool detected: ${toolName}, item type: ${this.getItemType(item)}`,
          );

          const mapKey = typeof item.id === 'string' && item.id.length > 0 ? item.id : randomUUID();
          let toolState = activeTools.get(mapKey);
          const latestInput = this.buildToolInputPayload(item);

          if (!toolState) {
            toolState = {
              toolCallId: mapKey,
              toolName,
              inputPayload: latestInput,
              hasEmittedCall: false,
            };
            activeTools.set(mapKey, toolState);
          } else {
            toolState.toolName = toolName;
            if (latestInput !== undefined) {
              toolState.inputPayload = latestInput;
            }
          }

          if (!toolState.hasEmittedCall) {
            this.logger.debug(`[codex-cli] Emitting tool invocation: ${toolState.toolName}`);
            this.emitToolInvocation(
              controller,
              toolState.toolCallId,
              toolState.toolName,
              toolState.inputPayload,
            );
            toolState.hasEmittedCall = true;
          }

          if (event.type === 'item.completed') {
            const { result, metadata } = this.buildToolResultPayload(item);
            this.logger.debug(`[codex-cli] Tool completed: ${toolState.toolName}`);
            this.emitToolResult(
              controller,
              toolState.toolCallId,
              toolState.toolName,
              item,
              result,
              metadata,
            );
            activeTools.delete(mapKey);
          }
        };

        // Abort support
        const onAbort = () => {
          child.kill('SIGTERM');
        };
        if (options.abortSignal) {
          if (options.abortSignal.aborted) {
            child.kill('SIGTERM');
            controller.error(options.abortSignal.reason ?? new Error('Request aborted'));
            return;
          }
          options.abortSignal.addEventListener('abort', onAbort, { once: true });
        }

        const finishStream = (code: number | null) => {
          const duration = Date.now() - startTime;

          if (code !== 0) {
            this.logger.error(
              `[codex-cli] Stream process exited with code ${code} after ${duration}ms`,
            );
            controller.error(
              createAPICallError({
                message: `Codex CLI exited with code ${code}`,
                exitCode: code ?? undefined,
                stderr,
                promptExcerpt,
              }),
            );
            return;
          }

          if (turnFailureMessage) {
            this.logger.error(`[codex-cli] Stream failed: ${turnFailureMessage}`);
            controller.error(
              createAPICallError({
                message: turnFailureMessage,
                stderr,
                promptExcerpt,
              }),
            );
            return;
          }

          // Emit text (non-streaming JSONL suppresses deltas; we send final text once)
          let finalText = accumulatedText;
          if (!finalText && lastMessagePath) {
            try {
              const fileText = readFileSync(lastMessagePath, 'utf8');
              if (fileText) finalText = fileText.trim();
            } catch {}
            if (lastMessageIsTemp) {
              try {
                rmSync(lastMessagePath, { force: true });
              } catch {}
            }
          }

          // No JSON extraction needed - native schema guarantees valid JSON
          if (finalText) {
            const textId = randomUUID();
            controller.enqueue({ type: 'text-start', id: textId });
            controller.enqueue({ type: 'text-delta', id: textId, delta: finalText });
            controller.enqueue({ type: 'text-end', id: textId });
          }

          const usageSummary = lastUsage ?? { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
          this.logger.info(
            `[codex-cli] Stream completed - Session: ${this.sessionId ?? 'N/A'}, Duration: ${duration}ms, Tokens: ${usageSummary.totalTokens}`,
          );
          this.logger.debug(
            `[codex-cli] Token usage - Input: ${usageSummary.inputTokens}, Output: ${usageSummary.outputTokens}, Total: ${usageSummary.totalTokens}`,
          );
          controller.enqueue({
            type: 'finish',
            finishReason: 'stop',
            usage: usageSummary,
          });
          controller.close();
        };

        child.stderr.on('data', (d) => (stderr += String(d)));
        child.stdout.setEncoding('utf8');
        child.stdout.on('data', (chunk: string) => {
          const lines = chunk.split(/\r?\n/).filter(Boolean);
          for (const line of lines) {
            const event = this.parseExperimentalJsonEvent(line);
            if (!event) continue;

            this.logger.debug(`[codex-cli] Stream event: ${event.type ?? 'unknown'}`);

            if (event.type === 'thread.started' && typeof event.thread_id === 'string') {
              this.sessionId = event.thread_id;
              this.logger.debug(`[codex-cli] Stream session started: ${this.sessionId}`);
              if (!responseMetadataSent) {
                responseMetadataSent = true;
                sendMetadata();
              }
              continue;
            }

            if (event.type === 'session.created' && typeof event.session_id === 'string') {
              this.sessionId = event.session_id;
              this.logger.debug(`[codex-cli] Stream session created: ${this.sessionId}`);
              if (!responseMetadataSent) {
                responseMetadataSent = true;
                sendMetadata();
              }
              continue;
            }

            if (event.type === 'turn.completed') {
              const usageEvent = this.extractUsage(event);
              if (usageEvent) {
                lastUsage = usageEvent;
              }
              continue;
            }

            if (event.type === 'turn.failed') {
              const errorText =
                (event.error && typeof event.error.message === 'string' && event.error.message) ||
                (typeof event.message === 'string' ? event.message : undefined);
              turnFailureMessage = errorText ?? turnFailureMessage ?? 'Codex turn failed';
              this.logger.error(`[codex-cli] Stream turn failed: ${turnFailureMessage}`);
              sendMetadata({ error: turnFailureMessage });
              continue;
            }

            if (event.type === 'error') {
              const errorText = typeof event.message === 'string' ? event.message : undefined;
              const effective = errorText ?? 'Codex error';
              turnFailureMessage = turnFailureMessage ?? effective;
              this.logger.error(`[codex-cli] Stream error event: ${effective}`);
              sendMetadata({ error: effective });
              continue;
            }

            if (event.type && event.type.startsWith('item.')) {
              handleItemEvent(event);
            }
          }
        });

        const cleanupSchema = () => {
          if (!schemaPath) return;
          try {
            const schemaDir = dirname(schemaPath);
            rmSync(schemaDir, { recursive: true, force: true });
          } catch {}
        };

        child.on('error', (e) => {
          this.logger.error(`[codex-cli] Stream spawn error: ${String(e)}`);
          if (options.abortSignal) options.abortSignal.removeEventListener('abort', onAbort);
          cleanupSchema();
          controller.error(this.handleSpawnError(e, promptExcerpt));
        });
        child.on('close', (code) => {
          if (options.abortSignal) options.abortSignal.removeEventListener('abort', onAbort);

          // Clean up temp schema file
          cleanupSchema();

          // Use setImmediate to ensure all stdout 'data' events are processed first
          setImmediate(() => finishStream(code));
        });
      },
      cancel: () => {},
    });

    return { stream, request: { body: promptText } } as Awaited<
      ReturnType<LanguageModelV2['doStream']>
    >;
  }
}</doc><doc title="Codex Cli Provider" desc="docs page.">import type { LanguageModelV2, ProviderV2 } from '@ai-sdk/provider';
import { NoSuchModelError } from '@ai-sdk/provider';
import { CodexCliLanguageModel } from './codex-cli-language-model.js';
import type { CodexCliProviderSettings, CodexCliSettings } from './types.js';
import { getLogger } from './logger.js';
import { validateSettings } from './validation.js';

export interface CodexCliProvider extends ProviderV2 {
  (modelId: string, settings?: CodexCliSettings): LanguageModelV2;
  languageModel(modelId: string, settings?: CodexCliSettings): LanguageModelV2;
  chat(modelId: string, settings?: CodexCliSettings): LanguageModelV2;
  textEmbeddingModel(modelId: string): never;
  imageModel(modelId: string): never;
}

export function createCodexCli(options: CodexCliProviderSettings = {}): CodexCliProvider {
  const logger = getLogger(options.defaultSettings?.logger);

  if (options.defaultSettings) {
    const v = validateSettings(options.defaultSettings);
    if (!v.valid) {
      throw new Error(`Invalid default settings: ${v.errors.join(', ')}`);
    }
    for (const w of v.warnings) logger.warn(`Codex CLI Provider: ${w}`);
  }

  const createModel = (modelId: string, settings: CodexCliSettings = {}): LanguageModelV2 => {
    const merged: CodexCliSettings = { ...options.defaultSettings, ...settings };
    const v = validateSettings(merged);
    if (!v.valid) throw new Error(`Invalid settings: ${v.errors.join(', ')}`);
    for (const w of v.warnings) logger.warn(`Codex CLI: ${w}`);
    return new CodexCliLanguageModel({ id: modelId, settings: merged });
  };

  const provider = function (modelId: string, settings?: CodexCliSettings) {
    if (new.target) throw new Error('The Codex CLI provider function cannot be called with new.');
    return createModel(modelId, settings);
  } as CodexCliProvider;

  provider.languageModel = createModel;
  provider.chat = createModel;
  provider.textEmbeddingModel = ((modelId: string) => {
    throw new NoSuchModelError({ modelId, modelType: 'textEmbeddingModel' });
  }) as never;
  provider.imageModel = ((modelId: string) => {
    throw new NoSuchModelError({ modelId, modelType: 'imageModel' });
  }) as never;

  return provider;
}

export const codexCli = createCodexCli();</doc><doc title="Errors" desc="docs page.">import { APICallError, LoadAPIKeyError } from '@ai-sdk/provider';

export interface CodexErrorMetadata {
  code?: string;
  exitCode?: number;
  stderr?: string;
  promptExcerpt?: string;
}

export function createAPICallError({
  message,
  code,
  exitCode,
  stderr,
  promptExcerpt,
  isRetryable = false,
}: CodexErrorMetadata & { message: string; isRetryable?: boolean }): APICallError {
  const data: CodexErrorMetadata = { code, exitCode, stderr, promptExcerpt };
  return new APICallError({
    message,
    isRetryable,
    url: 'codex-cli://exec',
    requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : undefined,
    data,
  });
}

export function createAuthenticationError(message?: string): LoadAPIKeyError {
  return new LoadAPIKeyError({
    message: message || 'Authentication failed. Ensure Codex CLI is logged in (codex login).',
  });
}

export function isAuthenticationError(err: unknown): boolean {
  if (err instanceof LoadAPIKeyError) return true;
  if (err instanceof APICallError) {
    const data = err.data as CodexErrorMetadata | undefined;
    if (data?.exitCode === 401) return true;
  }
  return false;
}</doc><doc title="Index" desc="docs page.">export { createCodexCli, codexCli } from './codex-cli-provider.js';
export type { CodexCliProvider } from './codex-cli-provider.js';

export type {
  CodexCliSettings,
  CodexCliProviderSettings,
  CodexCliProviderOptions,
  Logger,
  ReasoningEffort,
  ReasoningSummary,
  ReasoningSummaryFormat,
  ModelVerbosity,
} from './types.js';

export { CodexCliLanguageModel } from './codex-cli-language-model.js';

// Error helpers
export { isAuthenticationError } from './errors.js';</doc><doc title="Logger" desc="docs page.">import type { Logger } from './types.js';

/**
 * Default logger that uses console with level tags.
 */
/* eslint-disable no-console -- Default logger legitimately uses all console methods */
const defaultLogger: Logger = {
  debug: (message: string) => console.debug(`[DEBUG] ${message}`),
  info: (message: string) => console.info(`[INFO] ${message}`),
  warn: (message: string) => console.warn(`[WARN] ${message}`),
  error: (message: string) => console.error(`[ERROR] ${message}`),
};
/* eslint-enable no-console */

/**
 * No-op logger that discards all messages.
 */
const noopLogger: Logger = {
  debug: () => {},
  info: () => {},
  warn: () => {},
  error: () => {},
};

/**
 * Gets the appropriate logger based on configuration.
 *
 * @param logger - Logger configuration from settings
 * @returns The logger to use
 */
export function getLogger(logger: Logger | false | undefined): Logger {
  if (logger === false) {
    return noopLogger;
  }

  if (logger === undefined) {
    return defaultLogger;
  }

  return logger;
}

/**
 * Creates a verbose-aware logger that only logs debug/info when verbose is enabled.
 * Warn and error are always logged regardless of verbose setting.
 *
 * @param logger - Base logger to wrap
 * @param verbose - Whether to enable verbose (debug/info) logging
 * @returns Logger with verbose-aware behavior
 */
export function createVerboseLogger(logger: Logger, verbose: boolean = false): Logger {
  if (verbose) {
    // When verbose is enabled, use all log levels
    return logger;
  }

  // When verbose is disabled, only allow warn/error
  // Bind methods to preserve 'this' context for class-based loggers
  return {
    debug: () => {}, // No-op when not verbose
    info: () => {}, // No-op when not verbose
    warn: logger.warn.bind(logger),
    error: logger.error.bind(logger),
  };
}</doc><doc title="Message Mapper" desc="docs page.">import type { ModelMessage } from 'ai';

type TextPart = { type: 'text'; text: string };
type ImagePart = { type: 'image'; [k: string]: unknown };
type ToolOutputText = { type: 'text'; value: string };
type ToolOutputJson = { type: 'json'; value: unknown };
type ToolItem = { toolName: string; output: ToolOutputText | ToolOutputJson };

function isTextPart(p: unknown): p is TextPart {
  return (
    typeof p === 'object' &&
    p !== null &&
    'type' in p &&
    (p as { type?: unknown }).type === 'text' &&
    'text' in p &&
    typeof (p as { text?: unknown }).text === 'string'
  );
}

function isImagePart(p: unknown): p is ImagePart {
  return (
    typeof p === 'object' && p !== null && 'type' in p && (p as { type?: unknown }).type === 'image'
  );
}

function isToolItem(p: unknown): p is ToolItem {
  if (typeof p !== 'object' || p === null) return false;
  const obj = p as { toolName?: unknown; output?: unknown };
  if (typeof obj.toolName !== 'string') return false;
  const out = obj.output as { type?: unknown; value?: unknown } | undefined;
  if (!out || (out.type !== 'text' && out.type !== 'json')) return false;
  if (out.type === 'text' && typeof out.value !== 'string') return false;
  return true;
}

export function mapMessagesToPrompt(prompt: readonly ModelMessage[]): {
  promptText: string;
  warnings?: string[];
} {
  const warnings: string[] = [];
  const parts: string[] = [];

  let systemText: string | undefined;

  for (const msg of prompt) {
    if (msg.role === 'system') {
      systemText = typeof msg.content === 'string' ? msg.content : String(msg.content);
      continue;
    }

    if (msg.role === 'user') {
      if (typeof msg.content === 'string') {
        parts.push(`Human: ${msg.content}`);
      } else if (Array.isArray(msg.content)) {
        const text = msg.content
          .filter(isTextPart)
          .map((p) => p.text)
          .join('\n');
        if (text) parts.push(`Human: ${text}`);
        const images = msg.content.filter(isImagePart);
        if (images.length) warnings.push('Image inputs ignored by Codex CLI integration.');
      }
      continue;
    }

    if (msg.role === 'assistant') {
      if (typeof msg.content === 'string') {
        parts.push(`Assistant: ${msg.content}`);
      } else if (Array.isArray(msg.content)) {
        const text = msg.content
          .filter(isTextPart)
          .map((p) => p.text)
          .join('\n');
        if (text) parts.push(`Assistant: ${text}`);
      }
      continue;
    }

    if (msg.role === 'tool') {
      if (Array.isArray(msg.content)) {
        for (const maybeTool of msg.content) {
          if (!isToolItem(maybeTool)) continue;
          const value =
            maybeTool.output.type === 'text'
              ? maybeTool.output.value
              : JSON.stringify(maybeTool.output.value);
          parts.push(`Tool Result (${maybeTool.toolName}): ${value}`);
        }
      }
      continue;
    }
  }

  let promptText = '';
  if (systemText) promptText += systemText + '\n\n';
  promptText += parts.join('\n\n');

  return { promptText, ...(warnings.length ? { warnings } : {}) };
}</doc><doc title="Types" desc="docs page.">// Types and settings for Codex CLI provider

/**
 * Logger interface for custom logging.
 * Allows consumers to provide their own logging implementation
 * or disable logging entirely.
 *
 * @example
 * ```typescript
 * const customLogger: Logger = {
 *   debug: (message) => myLoggingService.debug(message),
 *   info: (message) => myLoggingService.info(message),
 *   warn: (message) => myLoggingService.warn(message),
 *   error: (message) => myLoggingService.error(message),
 * };
 * ```
 */
export interface Logger {
  /**
   * Log a debug message. Only logged when verbose mode is enabled.
   * Used for detailed execution tracing and troubleshooting.
   */
  debug: (message: string) => void;

  /**
   * Log an informational message. Only logged when verbose mode is enabled.
   * Used for general execution flow information.
   */
  info: (message: string) => void;

  /**
   * Log a warning message.
   */
  warn: (message: string) => void;

  /**
   * Log an error message.
   */
  error: (message: string) => void;
}

export type ApprovalMode = 'untrusted' | 'on-failure' | 'on-request' | 'never';

export type SandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access';

export type ReasoningEffort = 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
/**
 * Reasoning summary detail level.
 * Note: The API error messages claim 'concise' and 'none' are valid, but they are
 * actually rejected with 400 errors. Only 'auto' and 'detailed' work in practice.
 */
export type ReasoningSummary = 'auto' | 'detailed';
export type ReasoningSummaryFormat = 'none' | 'experimental';
export type ModelVerbosity = 'low' | 'medium' | 'high';

export interface McpServerBase {
  /**
   * Enable/disable this MCP server without removing its definition.
   * Maps to: `mcp_servers.<name>.enabled`
   */
  enabled?: boolean;

  /**
   * Time allowed for the MCP server to start (in seconds).
   * Maps to: `mcp_servers.<name>.startup_timeout_sec`
   */
  startupTimeoutSec?: number;

  /**
   * Max time a single MCP tool call may run (in seconds).
   * Maps to: `mcp_servers.<name>.tool_timeout_sec`
   */
  toolTimeoutSec?: number;

  /**
   * Explicit allow/deny lists for tools exposed by the server.
   * Maps to: `mcp_servers.<name>.enabled_tools` / `disabled_tools`
   */
  enabledTools?: string[];
  disabledTools?: string[];
}

export interface McpServerStdio extends McpServerBase {
  /** Execute an MCP server over stdio */
  transport: 'stdio';

  /** Command to start the MCP server (e.g., `node`, `python`, or a binary path). */
  command: string;

  /** Arguments passed to the command. */
  args?: string[];

  /** Environment variables passed to the MCP process. */
  env?: Record<string, string>;

  /** Optional working directory for the MCP server process. */
  cwd?: string;
}

export interface McpServerHttp extends McpServerBase {
  /** Use an HTTP-based MCP server (RMCP). */
  transport: 'http';

  /** Base URL for the MCP server. */
  url: string;

  /** Bearer token supplied inline (use env var variant to avoid embedding secrets). */
  bearerToken?: string;

  /** Name of env var that holds the bearer token. */
  bearerTokenEnvVar?: string;

  /** Static HTTP headers to send with each MCP request. */
  httpHeaders?: Record<string, string>;

  /** Names of env vars whose values should be sent as HTTP headers. */
  envHttpHeaders?: Record<string, string>;
}

export type McpServerConfig = McpServerStdio | McpServerHttp;

export interface CodexCliSettings {
  // Path to the codex CLI JS entry (bin/codex.js) or executable. If omitted, the provider tries to resolve @openai/codex.
  codexPath?: string;

  // Set working directory for the Codex process
  cwd?: string;

  // Additional directories Codex should be allowed to read/write (maps to repeated --add-dir)
  addDirs?: string[];

  // Approval policy for command execution
  approvalMode?: ApprovalMode;

  // Sandbox mode for command execution
  sandboxMode?: SandboxMode;

  // Convenience: fully auto (equivalent to --full-auto)
  fullAuto?: boolean;

  // Danger mode which bypasses approvals and sandbox (equivalent to --dangerously-bypass-approvals-and-sandbox)
  dangerouslyBypassApprovalsAndSandbox?: boolean;

  // Skip Git repo safety check (recommended for CI/non-repo usage)
  skipGitRepoCheck?: boolean;

  // Force color handling in Codex CLI output; defaults to auto
  color?: 'always' | 'never' | 'auto';

  // Allow falling back to `npx @openai/codex` if the binary cannot be resolved
  allowNpx?: boolean;

  // Optional: write last agent message to this file (Codex CLI flag)
  outputLastMessageFile?: string;

  // Extra environment variables for the spawned process (e.g., OPENAI_API_KEY)
  env?: Record<string, string>;

  // Enable verbose provider logging
  verbose?: boolean;

  // Custom logger; set to false to disable logging
  logger?: Logger | false;

  // ===== Reasoning & Verbosity =====

  /**
   * Controls reasoning effort for reasoning-capable models (o3, o4-mini, the GPT-5.1 family,
   * and legacy GPT-5 slugs). Higher effort produces more thorough reasoning at the cost of latency.
   *
   * Codex CLI model presets currently expose `low`/`medium`/`high` for `gpt-5.1` and `gpt-5.1-codex`.
   * `gpt-5.1-codex-max` additionally supports `xhigh`. `gpt-5.1-codex-mini` only offers `medium`/`high`.
   * The legacy `gpt-5` slug still allowed `minimal`, but GPT-5.1 rejects it.
   *
   * Maps to: `-c model_reasoning_effort=<value>`
   * @see https://platform.openai.com/docs/guides/reasoning
   */
  reasoningEffort?: ReasoningEffort;

  /**
   * Controls reasoning summary detail level.
   *
   * Valid values: 'auto' | 'detailed'
   * Note: Despite API error messages claiming 'concise' and 'none' are valid,
   * they are rejected with 400 errors in practice.
   *
   * Maps to: `-c model_reasoning_summary=<value>`
   * @see https://platform.openai.com/docs/guides/reasoning#reasoning-summaries
   */
  reasoningSummary?: ReasoningSummary;

  /**
   * Controls reasoning summary format (experimental).
   *
   * Maps to: `-c model_reasoning_summary_format=<value>`
   */
  reasoningSummaryFormat?: ReasoningSummaryFormat;

  /**
   * Controls output length/detail for GPT-5.1 (non-Codex) and legacy GPT-5 models.
   * Codex-specific slugs ignore this flag because the CLI disables verbosity for them.
   * Only applies to models using the Responses API.
   *
   * Maps to: `-c model_verbosity=<value>`
   */
  modelVerbosity?: ModelVerbosity;

  // ===== MCP configuration =====

  /**
   * Configure MCP servers (stdio or HTTP/RMCP). Keys are server names.
   * Each entry maps to the Codex CLI `mcp_servers.<name>` table.
   */
  mcpServers?: Record<string, McpServerConfig>;

  /**
   * Enable the RMCP client so HTTP-based MCP servers can be contacted.
   * Maps to: `-c features.rmcp_client=true`
   */
  rmcpClient?: boolean;

  // ===== Advanced Codex Features =====

  /**
   * Include experimental plan tool that the model can use to update its current plan.
   *
   * Maps to: `--include-plan-tool`
   */
  includePlanTool?: boolean;

  /**
   * Configuration profile from config.toml to specify default options.
   *
   * Maps to: `--profile <name>`
   */
  profile?: string;

  /**
   * Use OSS provider (experimental).
   *
   * Maps to: `--oss`
   */
  oss?: boolean;

  /**
   * Enable web search tool for the model.
   *
   * Maps to: `-c tools.web_search=true`
   */
  webSearch?: boolean;

  // ===== Generic config overrides (maps to -c key=value) =====

  /**
   * Generic Codex CLI config overrides. Allows setting any config value
   * without updating the provider.
   *
   * Each entry maps to: `-c <key>=<value>`
   *
   * Examples:
   * - `{ experimental_resume: '/tmp/session.jsonl' }`
   * - `{ 'model_providers.custom.base_url': 'http://localhost:8000' }`
   * - `{ 'sandbox_workspace_write': { network_access: true } }`
   *
   * Values are serialized:
   * - string → raw string
   * - number/boolean → String(value)
   * - plain objects → flattened recursively to dotted keys
   * - arrays → JSON.stringify(value)
   * - other objects (Date, RegExp, Map, etc.) → JSON.stringify(value)
   */
  configOverrides?: Record<string, string | number | boolean | object>;
}

export interface CodexCliProviderSettings {
  // Default settings applied to language models created by this provider
  defaultSettings?: CodexCliSettings;
}

/**
 * Per-call overrides supplied through AI SDK providerOptions.
 * These values take precedence over constructor-level CodexCliSettings.
 */
export interface CodexCliProviderOptions {
  /**
   * Per-call override for reasoning depth.
   * Maps to `model_reasoning_effort`.
   */
  reasoningEffort?: ReasoningEffort;

  /**
   * Per-call override for reasoning summary detail level.
   * Maps to `model_reasoning_summary`.
   */
  reasoningSummary?: ReasoningSummary;

  /**
   * Per-call override for reasoning summary format.
   * Maps to `model_reasoning_summary_format`.
   */
  reasoningSummaryFormat?: ReasoningSummaryFormat;

  /**
   * AI SDK naming for per-call verbosity overrides.
   * Maps to Codex `model_verbosity`.
   */
  textVerbosity?: ModelVerbosity;

  /**
   * Per-call override for extra directories Codex can access.
   * Maps to repeated `--add-dir` flags.
   */
  addDirs?: string[];

  /**
   * Per-call Codex CLI config overrides. These are merged with
   * constructor-level overrides with per-call values taking precedence.
   */
  configOverrides?: Record<string, string | number | boolean | object>;

  /**
   * Per-call MCP server definitions. Merged with constructor definitions
   * (per-call servers and fields take precedence).
   */
  mcpServers?: Record<string, McpServerConfig>;

  /**
   * Per-call override for RMCP client enablement.
   */
  rmcpClient?: boolean;
}</doc><doc title="Validation" desc="docs page.">import { z } from 'zod';
import type { CodexCliSettings } from './types.js';

const loggerFunctionSchema = z.object({
  debug: z.any().refine((val) => typeof val === 'function', {
    message: 'debug must be a function',
  }),
  info: z.any().refine((val) => typeof val === 'function', {
    message: 'info must be a function',
  }),
  warn: z.any().refine((val) => typeof val === 'function', {
    message: 'warn must be a function',
  }),
  error: z.any().refine((val) => typeof val === 'function', {
    message: 'error must be a function',
  }),
});

const mcpServerBaseSchema = z.object({
  enabled: z.boolean().optional(),
  startupTimeoutSec: z.number().int().positive().optional(),
  toolTimeoutSec: z.number().int().positive().optional(),
  enabledTools: z.array(z.string()).optional(),
  disabledTools: z.array(z.string()).optional(),
});

const mcpServerStdioSchema = mcpServerBaseSchema.extend({
  transport: z.literal('stdio'),
  command: z.string().min(1),
  args: z.array(z.string()).optional(),
  env: z.record(z.string(), z.string()).optional(),
  cwd: z.string().optional(),
});

const mcpServerHttpSchema = mcpServerBaseSchema.extend({
  transport: z.literal('http'),
  url: z.string().min(1),
  bearerToken: z.string().optional(),
  bearerTokenEnvVar: z.string().optional(),
  httpHeaders: z.record(z.string(), z.string()).optional(),
  envHttpHeaders: z.record(z.string(), z.string()).optional(),
});

const mcpServerSchema = z.discriminatedUnion('transport', [
  mcpServerStdioSchema,
  mcpServerHttpSchema,
]);

export const mcpServersSchema = z.record(z.string(), mcpServerSchema);

const settingsSchema = z
  .object({
    codexPath: z.string().optional(),
    cwd: z.string().optional(),
    addDirs: z.array(z.string().min(1)).optional(),
    approvalMode: z.enum(['untrusted', 'on-failure', 'on-request', 'never']).optional(),
    sandboxMode: z.enum(['read-only', 'workspace-write', 'danger-full-access']).optional(),
    fullAuto: z.boolean().optional(),
    dangerouslyBypassApprovalsAndSandbox: z.boolean().optional(),
    skipGitRepoCheck: z.boolean().optional(),
    color: z.enum(['always', 'never', 'auto']).optional(),
    allowNpx: z.boolean().optional(),
    outputLastMessageFile: z.string().optional(),
    env: z.record(z.string(), z.string()).optional(),
    verbose: z.boolean().optional(),
    logger: z.union([z.literal(false), loggerFunctionSchema]).optional(),

    // NEW: Reasoning & Verbosity
    reasoningEffort: z.enum(['minimal', 'low', 'medium', 'high', 'xhigh']).optional(),
    // Note: API rejects 'concise' and 'none' despite error messages claiming they're valid
    reasoningSummary: z.enum(['auto', 'detailed']).optional(),
    reasoningSummaryFormat: z.enum(['none', 'experimental']).optional(),
    modelVerbosity: z.enum(['low', 'medium', 'high']).optional(),

    // NEW: Advanced features
    includePlanTool: z.boolean().optional(),
    profile: z.string().optional(),
    oss: z.boolean().optional(),
    webSearch: z.boolean().optional(),
    mcpServers: mcpServersSchema.optional(),
    rmcpClient: z.boolean().optional(),

    // NEW: Generic overrides
    configOverrides: z
      .record(
        z.string(),
        z.union([
          z.string(),
          z.number(),
          z.boolean(),
          z.object({}).passthrough(),
          z.array(z.any()),
        ]),
      )
      .optional(),
  })
  .strict();

export function validateSettings(settings: unknown): {
  valid: boolean;
  warnings: string[];
  errors: string[];
} {
  const warnings: string[] = [];
  const errors: string[] = [];

  const parsed = settingsSchema.safeParse(settings);
  if (!parsed.success) {
    // zod v3 => error.errors, zod v4 => error.issues
    type ZodIssueLike = { path?: (string | number)[]; message?: string };
    const raw = parsed.error as unknown;
    let issues: ZodIssueLike[] = [];
    if (raw && typeof raw === 'object') {
      const v4 = (raw as { issues?: unknown }).issues;
      const v3 = (raw as { errors?: unknown }).errors;
      if (Array.isArray(v4)) issues = v4 as ZodIssueLike[];
      else if (Array.isArray(v3)) issues = v3 as ZodIssueLike[];
    }
    for (const i of issues) {
      const path = Array.isArray(i?.path) ? i.path.join('.') : '';
      const message = i?.message || 'Invalid value';
      errors.push(`${path ? path + ': ' : ''}${message}`);
    }
    return { valid: false, warnings, errors };
  }

  const s = parsed.data as CodexCliSettings;
  if (s.fullAuto && s.dangerouslyBypassApprovalsAndSandbox) {
    warnings.push(
      'Both fullAuto and dangerouslyBypassApprovalsAndSandbox specified; fullAuto takes precedence.',
    );
  }

  // Note: Previously warned about reasoningSummary='none', but 'none' is now rejected
  // by the schema as an invalid value (only 'auto' and 'detailed' are accepted)

  return { valid: true, warnings, errors };
}

export function validateModelId(modelId: string): string | undefined {
  if (!modelId || modelId.trim() === '') return 'Model ID cannot be empty';
  // We don’t restrict model values here; Codex forwards to Responses API
  return undefined;
}</doc><doc title="Codex Cli Language Model.Test" desc="docs page.">import { describe, it, expect, vi, beforeEach } from 'vitest';
import { CodexCliLanguageModel } from '../codex-cli-language-model.js';
import { PassThrough } from 'node:stream';
import { EventEmitter } from 'node:events';
import { writeFileSync, mkdtempSync, readFileSync, existsSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

// Helper to create a mock spawn that emits JSONL events
function makeMockSpawn(lines: string[], exitCode = 0) {
  return vi.fn((_cmd: string, args: string[]) => {
    const child = new EventEmitter() as any;
    child.stdout = new PassThrough();
    child.stderr = new PassThrough();
    child.kill = vi.fn();

    // If our code passes --output-last-message <path>, write there too
    const idx = args.indexOf('--output-last-message');
    if (idx !== -1 && args[idx + 1]) {
      try {
        writeFileSync(args[idx + 1], 'Fallback last message\n');
      } catch {}
    }

    // emit lines asynchronously
    setTimeout(() => {
      for (const l of lines) child.stdout.write(l + '\n');
      child.stdout.end();
      child.emit('close', exitCode);
    }, 5);

    return child;
  });
}

// Mock child_process
vi.mock('node:child_process', async () => {
  let currentMock: (cmd: string, args: string[]) => any = makeMockSpawn([], 0) as any;
  const mod = {
    spawn: (cmd: string, args: string[]) => currentMock(cmd, args),
    __setSpawnMock: (fn: any) => {
      currentMock = fn;
    },
  } as any;
  return mod;
});

// Access the helper to swap mocks inside tests
const childProc = await import('node:child_process');

describe('CodexCliLanguageModel', () => {
  beforeEach(() => {
    vi.restoreAllMocks();
  });

  it('doGenerate returns text and sessionId from experimental JSON events', async () => {
    const lines = [
      JSON.stringify({
        type: 'thread.started',
        thread_id: 'thread-123',
      }),
      JSON.stringify({
        type: 'turn.completed',
        usage: { input_tokens: 10, output_tokens: 5 },
      }),
      JSON.stringify({
        type: 'item.completed',
        item: { item_type: 'assistant_message', text: 'Hello JSON' },
      }),
    ];
    (childProc as any).__setSpawnMock(makeMockSpawn(lines, 0));

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: { allowNpx: true, color: 'never' },
    });
    const res = await model.doGenerate({ prompt: [{ role: 'user', content: 'Say hi' }] as any });
    expect(res.content[0]).toMatchObject({ type: 'text', text: 'Hello JSON' });
    expect(res.providerMetadata?.['codex-cli']).toMatchObject({ sessionId: 'thread-123' });
    expect(res.usage).toMatchObject({ inputTokens: 10, outputTokens: 5, totalTokens: 15 });
  });

  it('doStream yields response-metadata, text-delta, finish', async () => {
    const lines = [
      JSON.stringify({ type: 'thread.started', thread_id: 'thread-1' }),
      JSON.stringify({
        type: 'item.completed',
        item: { item_type: 'assistant_message', text: 'Streamed hello' },
      }),
      JSON.stringify({ type: 'turn.completed', usage: { input_tokens: 0, output_tokens: 0 } }),
    ];
    (childProc as any).__setSpawnMock(makeMockSpawn(lines, 0));

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: { allowNpx: true, color: 'never' },
    });
    const { stream } = await model.doStream({
      prompt: [{ role: 'user', content: 'Say hi' }] as any,
    });

    const received: any[] = [];
    const _reader = (stream as any).getReader ? undefined : null; // ensure Web stream compat
    const rs = stream as ReadableStream<any>;
    const it = (rs as any)[Symbol.asyncIterator]();
    for await (const part of it) received.push(part);

    const types = received.map((p) => p.type);
    expect(types).toContain('response-metadata');
    expect(types).toContain('text-delta');
    expect(types).toContain('finish');
    const deltaPayload = received.find((p) => p.type === 'text-delta');
    expect(deltaPayload?.delta).toBe('Streamed hello');
  });

  it('streams tool events for command execution items', async () => {
    const lines = [
      JSON.stringify({ type: 'thread.started', thread_id: 'thread-tools' }),
      JSON.stringify({
        type: 'item.started',
        item: {
          id: 'item_0',
          item_type: 'command_execution',
          command: 'ls -la',
          aggregated_output: '',
          exit_code: null,
          status: 'in_progress',
        },
      }),
      JSON.stringify({
        type: 'item.completed',
        item: {
          id: 'item_0',
          item_type: 'command_execution',
          command: 'ls -la',
          aggregated_output: 'README.md\n',
          exit_code: 0,
          status: 'completed',
        },
      }),
      JSON.stringify({
        type: 'item.completed',
        item: { id: 'item_1', item_type: 'assistant_message', text: 'done' },
      }),
      JSON.stringify({
        type: 'turn.completed',
        usage: { input_tokens: 4, output_tokens: 2, cached_input_tokens: 1 },
      }),
    ];
    (childProc as any).__setSpawnMock(makeMockSpawn(lines, 0));

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: { allowNpx: true, color: 'never' },
    });
    const { stream } = await model.doStream({
      prompt: [{ role: 'user', content: 'List files' }] as any,
    });

    const received: any[] = [];
    const rs = stream as ReadableStream<any>;
    const iterator = (rs as any)[Symbol.asyncIterator]();
    for await (const part of iterator) received.push(part);

    const toolCall = received.find((p) => p.type === 'tool-call');
    expect(toolCall?.toolName).toBe('exec');
    expect(toolCall?.providerExecuted).toBe(true);
    expect(toolCall?.input).toContain('ls -la');

    const toolResult = received.find((p) => p.type === 'tool-result');
    expect(toolResult?.toolCallId).toBe(toolCall?.toolCallId);
    expect(toolResult?.result).toMatchObject({
      command: 'ls -la',
      aggregatedOutput: 'README.md\n',
      exitCode: 0,
      status: 'completed',
    });

    const finish = received.find((p) => p.type === 'finish');
    expect(finish?.usage).toEqual({
      inputTokens: 4,
      outputTokens: 2,
      totalTokens: 6,
      cachedInputTokens: 1,
    });
  });

  it('includes approval/sandbox flags and output-last-message; uses npx with allowNpx', async () => {
    let seen: any = { cmd: '', args: [] as string[] };
    const lines = [
      JSON.stringify({ type: 'thread.started', thread_id: 'thread-2' }),
      JSON.stringify({
        type: 'item.completed',
        item: { item_type: 'assistant_message', text: 'OK' },
      }),
    ];
    (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
      seen = { cmd, args };
      return makeMockSpawn(lines, 0)(cmd, args);
    });

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: {
        allowNpx: true,
        color: 'never',
        approvalMode: 'on-failure',
        sandboxMode: 'workspace-write',
        skipGitRepoCheck: true,
        outputLastMessageFile: join(mkdtempSync(join(tmpdir(), 'codex-test-')), 'last.txt'),
      },
    });

    await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

    expect(['npx', 'node']).toContain(seen.cmd);
    expect(seen.args).toContain('exec');
    expect(seen.args).toContain('--experimental-json');
    expect(seen.args).not.toContain('--json');
    expect(seen.args).toContain('-c');
    expect(seen.args).toContain('approval_policy=on-failure');
    expect(seen.args).toContain('sandbox_mode=workspace-write');
    expect(seen.args).toContain('--skip-git-repo-check');
    expect(seen.args).toContain('--output-last-message');
  });

  it('retains user-provided outputLastMessageFile when fallback is used', async () => {
    let outputPath = '';
    const lines = [JSON.stringify({ type: 'thread.started', thread_id: 'thread-last-user' })];
    (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
      const idx = args.indexOf('--output-last-message');
      outputPath = idx !== -1 ? args[idx + 1] : '';
      return makeMockSpawn(lines, 0)(cmd, args);
    });

    const dir = mkdtempSync(join(tmpdir(), 'codex-last-msg-user-'));
    const filePath = join(dir, 'last.txt');

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: {
        allowNpx: true,
        color: 'never',
        outputLastMessageFile: filePath,
      },
    });

    const res = await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

    expect(res.content[0]).toMatchObject({ type: 'text', text: 'Fallback last message' });
    expect(outputPath).toBe(filePath);
    expect(existsSync(filePath)).toBe(true);
    expect(readFileSync(filePath, 'utf8')).toContain('Fallback last message');
  });

  it('cleans up auto-created outputLastMessageFile after fallback', async () => {
    let outputPath = '';
    const lines = [JSON.stringify({ type: 'thread.started', thread_id: 'thread-last-auto' })];
    (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
      const idx = args.indexOf('--output-last-message');
      outputPath = idx !== -1 ? args[idx + 1] : '';
      return makeMockSpawn(lines, 0)(cmd, args);
    });

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: {
        allowNpx: true,
        color: 'never',
      },
    });

    const res = await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

    expect(res.content[0]).toMatchObject({ type: 'text', text: 'Fallback last message' });
    expect(outputPath).toBeTruthy();
    expect(existsSync(outputPath)).toBe(false);
  });

  it('sets isError for failed command execution', async () => {
    const lines = [
      JSON.stringify({ type: 'thread.started', thread_id: 'thread-fail' }),
      JSON.stringify({
        type: 'item.started',
        item: {
          id: 'item_fail',
          item_type: 'command_execution',
          command: 'false',
          aggregated_output: '',
          exit_code: null,
          status: 'in_progress',
        },
      }),
      JSON.stringify({
        type: 'item.completed',
        item: {
          id: 'item_fail',
          item_type: 'command_execution',
          command: 'false',
          aggregated_output: '',
          exit_code: 1,
          status: 'failed',
        },
      }),
      JSON.stringify({
        type: 'item.completed',
        item: { id: 'item_1', item_type: 'assistant_message', text: 'oops' },
      }),
      JSON.stringify({ type: 'turn.completed', usage: { input_tokens: 0, output_tokens: 0 } }),
    ];
    (childProc as any).__setSpawnMock(makeMockSpawn(lines, 0));

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: { allowNpx: true, color: 'never' },
    });
    const { stream } = await model.doStream({
      prompt: [{ role: 'user', content: 'fail please' }] as any,
    });

    const received: any[] = [];
    const rs = stream as ReadableStream<any>;
    for await (const part of (rs as any)[Symbol.asyncIterator]()) received.push(part);

    const toolResult = received.find((p) => p.type === 'tool-result');
    expect(toolResult?.isError).toBe(true);
    expect(toolResult?.toolName).toBe('exec');
    expect(toolResult?.result).toMatchObject({
      command: 'false',
      exitCode: 1,
      status: 'failed',
    });
  });

  it('uses --full-auto when specified and omits -c flags', async () => {
    let lastArgs: string[] = [];
    const lines = [
      JSON.stringify({
        id: '1',
        msg: {
          type: 'session_configured',
          session_id: 'sess-3',
          model: 'gpt-5',
          history_log_id: 0,
          history_entry_count: 0,
        },
      }),
      JSON.stringify({ id: '2', msg: { type: 'task_complete', last_agent_message: 'OK' } }),
    ];
    (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
      lastArgs = args;
      return makeMockSpawn(lines, 0)(cmd, args);
    });

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: { allowNpx: true, fullAuto: true, color: 'never' },
    });
    await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

    expect(lastArgs).toContain('--full-auto');
    // No -c flags when fullAuto
    expect(lastArgs.join(' ')).not.toMatch(/approval_policy|sandbox_mode/);
  });

  it('rejects with APICallError on non-zero exit', async () => {
    (childProc as any).__setSpawnMock(makeMockSpawn([], 2));
    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: { allowNpx: true, color: 'never' },
    });
    await expect(
      model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any }),
    ).rejects.toMatchObject({
      name: 'AI_APICallError',
      data: { exitCode: 2 },
    });
  });

  // Note: auth error mapping is covered via error helpers; CLI error path requires real process semantics.

  it('propagates pre-aborted signal reason and kills child', async () => {
    let killed = false;
    (childProc as any).__setSpawnMock((_cmd: string, _args: string[]) => {
      const child = new EventEmitter() as any;
      child.stdout = new PassThrough();
      child.stderr = new PassThrough();
      child.kill = vi.fn(() => {
        killed = true;
      });
      return child;
    });

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: { allowNpx: true, color: 'never' },
    });
    const ac = new AbortController();
    const reason = new Error('aborted');
    ac.abort(reason);
    await expect(
      model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        abortSignal: ac.signal,
      }),
    ).rejects.toBe(reason);
    expect(killed).toBe(true);
  });

  describe('Phase 1: constructor-level parameters', () => {
    it('emits -c model_reasoning_* and model_verbosity args when set', async () => {
      let seen: any = { args: [] as string[] };
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-reason' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        seen = { args };
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5-codex',
        settings: {
          allowNpx: true,
          color: 'never',
          reasoningEffort: 'high',
          reasoningSummary: 'detailed',
          reasoningSummaryFormat: 'experimental',
          modelVerbosity: 'low',
        },
      });
      await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

      const a = seen.args as string[];
      expect(a).toContain('-c');
      expect(a).toContain('model_reasoning_effort=high');
      expect(a).toContain('model_reasoning_summary=detailed');
      expect(a).toContain('model_reasoning_summary_format=experimental');
      expect(a).toContain('model_verbosity=low');
    });

    it('emits advanced feature flags (--include-plan-tool, --profile, --oss) and webSearch config', async () => {
      let seen: any = { args: [] as string[] };
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-adv' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        seen = { args };
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          includePlanTool: true,
          profile: 'production',
          oss: true,
          webSearch: true,
          color: 'never',
        },
      });
      await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

      const a = seen.args as string[];
      expect(a).toContain('--include-plan-tool');
      expect(a).toContain('--profile');
      expect(a).toContain('production');
      expect(a).toContain('--oss');
      expect(a).toContain('tools.web_search=true');
    });

    it('emits MCP stdio server config and rmcp client toggle', async () => {
      let captured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-mcp' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        captured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          rmcpClient: true,
          mcpServers: {
            files: {
              transport: 'stdio',
              command: 'node',
              args: ['mcp.js'],
              env: { API_KEY: 'abc' },
              cwd: '/tmp/mcp',
              enabled: true,
              startupTimeoutSec: 5,
              toolTimeoutSec: 15,
              enabledTools: ['list'],
              disabledTools: ['write'],
            },
          },
        },
      });

      await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

      expect(captured).toContain('features.rmcp_client=true');
      expect(captured).toContain('mcp_servers.files.command=node');
      expect(captured).toContain('mcp_servers.files.args=["mcp.js"]');
      expect(captured).toContain('mcp_servers.files.env.API_KEY=abc');
      expect(captured).toContain('mcp_servers.files.cwd=/tmp/mcp');
      expect(captured).toContain('mcp_servers.files.enabled=true');
      expect(captured).toContain('mcp_servers.files.startup_timeout_sec=5');
      expect(captured).toContain('mcp_servers.files.tool_timeout_sec=15');
      expect(captured).toContain('mcp_servers.files.enabled_tools=["list"]');
      expect(captured).toContain('mcp_servers.files.disabled_tools=["write"]');
    });

    it('emits --add-dir for each additional directory', async () => {
      let captured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-add-dir' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        captured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          addDirs: ['../shared', '/tmp/lib'],
        },
      });

      await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

      const addDirFlags = captured.filter((v) => v === '--add-dir');
      expect(addDirFlags).toHaveLength(2);
      expect(captured).toContain('../shared');
      expect(captured).toContain('/tmp/lib');
    });

    it('emits -c for configOverrides with string, number, boolean, and object', async () => {
      let seen: any = { args: [] as string[] };
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-over' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        seen = { args };
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          configOverrides: {
            experimental_resume: '/tmp/session.jsonl',
            hide_agent_reasoning: true,
            model_context_window: 200000,
            sandbox_workspace_write: { network_access: true },
          },
        },
      });
      await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

      const a = seen.args as string[];
      expect(a).toContain('experimental_resume=/tmp/session.jsonl');
      expect(a).toContain('hide_agent_reasoning=true');
      expect(a).toContain('model_context_window=200000');
      expect(a).toContain('sandbox_workspace_write.network_access=true');
    });

    it('handles deep nesting, arrays, and dotted keys in configOverrides', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-over-2' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          configOverrides: {
            deep: { nested: { value: true } },
            arr: [1, 2],
            'dotted.key': 'val',
          },
        },
      });
      await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

      expect(argsCaptured).toContain('deep.nested.value=true');
      expect(argsCaptured).toContain('arr=[1,2]');
      expect(argsCaptured).toContain('dotted.key=val');
    });

    it('keeps reasoning flags when fullAuto is enabled (but omits approval/sandbox overrides)', async () => {
      let lastArgs: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-fa' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        lastArgs = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: { allowNpx: true, color: 'never', fullAuto: true, reasoningEffort: 'medium' },
      });
      await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

      expect(lastArgs).toContain('--full-auto');
      expect(lastArgs.join(' ')).not.toMatch(/approval_policy|sandbox_mode/);
      expect(lastArgs).toContain('model_reasoning_effort=medium');
    });
  });

  describe('Phase 2: providerOptions overrides', () => {
    it('overrides constructor reasoning settings per call', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-phase2' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5-codex',
        settings: {
          allowNpx: true,
          color: 'never',
          reasoningEffort: 'low',
        },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            reasoningEffort: 'high',
            reasoningSummary: 'detailed',
          },
        },
      });

      expect(argsCaptured).toContain('model_reasoning_effort=high');
      expect(argsCaptured.join(' ')).not.toContain('model_reasoning_effort=low');
      expect(argsCaptured).toContain('model_reasoning_summary=detailed');
    });

    it('merges configOverrides with per-call overrides taking precedence', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-config' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          configOverrides: {
            setting1: 'value1',
            setting2: 'value2',
          },
        },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            configOverrides: {
              setting2: 'override',
              setting3: 'value3',
            },
          },
        },
      });

      expect(argsCaptured).toContain('setting1=value1');
      expect(argsCaptured).toContain('setting2=override');
      expect(argsCaptured.join(' ')).not.toContain('setting2=value2');
      expect(argsCaptured).toContain('setting3=value3');
    });

    it('merges MCP servers across constructor and providerOptions', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-mcp-merge' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          mcpServers: {
            local: {
              transport: 'stdio',
              command: 'node',
              args: ['base.js'],
              env: { BASE: '1' },
            },
          },
        },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            rmcpClient: true,
            mcpServers: {
              local: {
                transport: 'stdio',
                command: 'node',
                args: ['override.js'],
                env: { EXTRA: '2' },
              },
              remote: {
                transport: 'http',
                url: 'https://mcp.example',
                bearerTokenEnvVar: 'MCP_TOKEN',
                httpHeaders: { 'x-debug': '1' },
              },
            },
          },
        },
      });

      expect(argsCaptured).toContain('features.rmcp_client=true');
      expect(argsCaptured).toContain('mcp_servers.local.command=node');
      expect(argsCaptured).toContain('mcp_servers.local.args=["override.js"]');
      expect(argsCaptured).toContain('mcp_servers.local.env.BASE=1');
      expect(argsCaptured).toContain('mcp_servers.local.env.EXTRA=2');
      expect(argsCaptured).toContain('mcp_servers.remote.url=https://mcp.example');
      expect(argsCaptured).toContain('mcp_servers.remote.bearer_token_env_var=MCP_TOKEN');
      expect(argsCaptured).toContain('mcp_servers.remote.http_headers.x-debug=1');
    });

    it('allows clearing stdio MCP args and tool lists with empty arrays', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-mcp-empty' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          mcpServers: {
            local: {
              transport: 'stdio',
              command: 'node',
              args: ['base.js'],
              enabledTools: ['one'],
              disabledTools: ['two'],
            },
          },
        },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            mcpServers: {
              local: {
                transport: 'stdio',
                command: 'node',
                args: [],
                enabledTools: [],
                disabledTools: [],
              },
            },
          },
        },
      });

      expect(argsCaptured).toContain('mcp_servers.local.args=[]');
      expect(argsCaptured).toContain('mcp_servers.local.enabled_tools=[]');
      expect(argsCaptured).toContain('mcp_servers.local.disabled_tools=[]');
    });

    it('allows clearing HTTP MCP headers with empty objects', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-mcp-http-empty' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          mcpServers: {
            remote: {
              transport: 'http',
              url: 'https://base.example',
              httpHeaders: { 'x-base': '1' },
              envHttpHeaders: { BASE_ENV: 'BASE_ENV' },
            },
          },
        },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            mcpServers: {
              remote: {
                transport: 'http',
                url: 'https://base.example',
                httpHeaders: {},
                envHttpHeaders: {},
              },
            },
          },
        },
      });

      expect(argsCaptured).toContain('mcp_servers.remote.http_headers={}');
      expect(argsCaptured).toContain('mcp_servers.remote.env_http_headers={}');
    });

    it('clears base bearerToken when overriding with bearerTokenEnvVar (auth bundle replacement)', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-auth-bundle' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          mcpServers: {
            remote: {
              transport: 'http',
              url: 'https://api.example.com',
              bearerToken: 'base-token-secret', // Should be cleared
            },
          },
        },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            mcpServers: {
              remote: {
                transport: 'http',
                url: 'https://api.example.com',
                bearerTokenEnvVar: 'NEW_ENV_VAR', // Should replace the token
              },
            },
          },
        },
      });

      // Should contain the new env var
      expect(argsCaptured).toContain('mcp_servers.remote.bearer_token_env_var=NEW_ENV_VAR');
      // Should NOT contain the old token
      expect(argsCaptured.some((arg) => arg.includes('mcp_servers.remote.bearer_token=base-token-secret'))).toBe(false);
    });

    it('merges addDirs from providerOptions with constructor settings', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-add-dir-override' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: { allowNpx: true, color: 'never', addDirs: ['./base'] },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            addDirs: ['../feature'],
          },
        },
      });

      const addDirFlags = argsCaptured.filter((v) => v === '--add-dir');
      expect(addDirFlags).toHaveLength(2);
      expect(argsCaptured).toContain('./base');
      expect(argsCaptured).toContain('../feature');
    });

    it('maps textVerbosity provider option to model_verbosity flag', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-verbosity' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: { allowNpx: true, color: 'never' },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            textVerbosity: 'high',
          },
        },
      });

      expect(argsCaptured).toContain('model_verbosity=high');
    });

    it('applies providerOptions during streaming calls', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-stream' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'stream ok' },
        }),
        JSON.stringify({ type: 'turn.completed', usage: { input_tokens: 0, output_tokens: 0 } }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: { allowNpx: true, color: 'never' },
      });

      const { stream } = await model.doStream({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            reasoningEffort: 'medium',
            textVerbosity: 'low',
          },
        },
      });

      const reader = (stream as any)[Symbol.asyncIterator]();
      for await (const _ of reader) {
        // exhaust stream to ensure spawn completes
      }

      expect(argsCaptured).toContain('model_reasoning_effort=medium');
      expect(argsCaptured).toContain('model_verbosity=low');
    });
  });
});</doc><doc title="Codex Cli Provider.Test" desc="docs page.">import { describe, it, expect } from 'vitest';
import { createCodexCli } from '../codex-cli-provider.js';

describe('createCodexCli', () => {
  it('creates a model with merged defaults', () => {
    const provider = createCodexCli({ defaultSettings: { skipGitRepoCheck: true } });
    const model: any = provider('gpt-5', { color: 'never' });
    expect(model.provider).toBe('codex-cli');
    expect(model.modelId).toBe('gpt-5');
  });

  it('accepts addDirs in defaultSettings', () => {
    const provider = createCodexCli({
      defaultSettings: { addDirs: ['../shared', '/tmp/lib'] },
    });
    const model: any = provider('gpt-5');
    expect(model.provider).toBe('codex-cli');
    expect(model.modelId).toBe('gpt-5');
  });

  it('accepts addDirs in per-model settings', () => {
    const provider = createCodexCli();
    const model: any = provider('gpt-5', { addDirs: ['../shared'] });
    expect(model.provider).toBe('codex-cli');
    expect(model.modelId).toBe('gpt-5');
  });

  it('accepts outputLastMessageFile in settings', () => {
    const provider = createCodexCli();
    const model: any = provider('gpt-5', { outputLastMessageFile: '/tmp/last.txt' });
    expect(model.provider).toBe('codex-cli');
    expect(model.modelId).toBe('gpt-5');
  });
});</doc></src></project>


## Links discovered
- [LIMITATIONS.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/LIMITATIONS.md)
- [full documentation](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-codex-cli/guide.md)
- [GitHub](https://github.com/ben-vargas/ai-sdk-provider-codex-cli/issues)
- [CHANGELOG](https://github.com/AcidicSoil/llms-txt-registry/blob/main/CHANGELOG.md)
- [LIMITATIONS.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/LIMITATIONS.md)

--- docs/ai-sdk-provider-codex-cli/ai-sdk-provider-codex-cli-llms-full.txt ---
# llms-full (private-aware)
> Built from GitHub files and website pages. Large files may be truncated.

--- docs/ai-sdk-v5/configuration.md ---
# Configuration Reference

This provider wraps the `codex exec` CLI in non‑interactive mode and maps settings to CLI flags/config overrides.

## Settings

- `allowNpx` (boolean): If true, runs `npx -y @openai/codex` when Codex isn’t found on PATH.
- `codexPath` (string): Explicit path to Codex JS entry (`bin/codex.js`), bypassing PATH resolution.
- `cwd` (string): Working directory for the spawned process.
- `addDirs` (string[]): Additional directories Codex can read/write. Emits one `--add-dir <path>` per entry (useful in monorepos or when sharing resources across packages).
- `color` ('always' | 'never' | 'auto'): Controls ANSI color emission.
- `skipGitRepoCheck` (boolean): When true, passes `--skip-git-repo-check`.
- `fullAuto` (boolean): Sets `--full-auto` (low-friction sandboxed execution).
- `dangerouslyBypassApprovalsAndSandbox` (boolean): Maps to `--dangerously-bypass-approvals-and-sandbox`.
- `approvalMode` ('untrusted' | 'on-failure' | 'on-request' | 'never'): Applied via `-c approval_policy=...`.
- `sandboxMode` ('read-only' | 'workspace-write' | 'danger-full-access'): Applied via `-c sandbox_mode=...`.
- `outputLastMessageFile` (string): File path to write the last agent message. If omitted, a temp file is created.
- `env` (Record<string,string>): Extra env vars for the child process (e.g., `OPENAI_API_KEY`).
- `verbose` (boolean): Enable verbose logging mode. When `true`, enables `debug` and `info` log levels. When `false` (default), only `warn` and `error` are logged.
- `logger` (Logger | false): Custom logger object or `false` to disable logging entirely. Logger must implement four methods: `debug`, `info`, `warn`, and `error`. Default uses `console.*` methods.
- `rmcpClient` (boolean): Enable the RMCP client so HTTP-based MCP servers can be reached (`-c features.rmcp_client=true`).
- `mcpServers` (Record<string, McpServerConfig>): Define MCP servers (stdio or HTTP). Keys are server names; values follow the shapes below.

## Model Parameters & Advanced Options (v0.4.0+)

### Reasoning & Verbosity

- **`reasoningEffort`** ('minimal' | 'low' | 'medium' | 'high' | 'xhigh'): Controls reasoning depth for reasoning-capable models (o3, o4-mini, the GPT-5.1 family, and legacy GPT-5). Higher effort produces more thorough reasoning at the cost of latency. Maps to `-c model_reasoning_effort=<value>`.
  - Per the Codex CLI model preset definitions (`codex-rs/common/src/model_presets.rs`), `gpt-5.1` and `gpt-5.1-codex` expose `low`, `medium`, and `high`; `gpt-5.1-codex-max` adds `xhigh`; and `gpt-5.1-codex-mini` only surfaces `medium` and `high`.
  - The older `gpt-5` slug still exposed `minimal`, but the GPT-5.1 family does not; passing `minimal` to a GPT-5.1 slug is rejected server-side.
- **`reasoningSummary`** ('auto' | 'detailed'): Controls reasoning summary detail level. **Note:** Despite API error messages claiming 'concise' and 'none' are valid, they are rejected with 400 errors. Only 'auto' and 'detailed' work. Maps to `-c model_reasoning_summary=<value>`.
- **`reasoningSummaryFormat`** ('none' | 'experimental'): Controls reasoning summary format (experimental). Maps to `-c model_reasoning_summary_format=<value>`.
- **`modelVerbosity`** ('low' | 'medium' | 'high'): Controls output length/detail for GPT-5.1 **non-Codex** models (and legacy GPT-5). Codex-specific slugs (`gpt-5.1-codex`, `gpt-5.1-codex-mini`) ignore this, because the CLI disables verbosity for those model families (`codex-rs/core/src/model_family.rs`). Maps to `-c model_verbosity=<value>` when supported.

### Advanced Codex Features

- **`includePlanTool`** (boolean): Include experimental plan tool that the model can use to update its current plan. Maps to `--include-plan-tool`.
- **`profile`** (string): Configuration profile from config.toml to specify default options. Maps to `--profile <name>`.
- **`oss`** (boolean): Use OSS provider (experimental). Maps to `--oss`.
- **`webSearch`** (boolean): Enable web search tool for the model. Maps to `-c tools.web_search=true`.

### MCP Servers (v0.6.0+)

- **`rmcpClient`** (boolean): Enables the RMCP client for HTTP-based MCP servers. Maps to `-c features.rmcp_client=true`.
- **`mcpServers`** (Record<string, McpServerConfig>): Define MCP servers by name.
  - Common fields: `enabled?`, `startupTimeoutSec?`, `toolTimeoutSec?`, `enabledTools?`, `disabledTools?`.
  - **Stdio servers** (`transport: 'stdio'`): `command` (required), `args?`, `env?`, `cwd?`.
  - **HTTP/RMCP servers** (`transport: 'http'`): `url` (required), `bearerToken?`, `bearerTokenEnvVar?`, `httpHeaders?`, `envHttpHeaders?`.

Example:

```ts
const model = codexCli('gpt-5.1-codex', {
  rmcpClient: true,
  mcpServers: {
    // Stdio MCP
    repo: {
      transport: 'stdio',
      command: 'node',
      args: ['tools/repo-mcp.js'],
      env: { API_KEY: process.env.REPO_KEY ?? '' },
      enabledTools: ['list', 'read'],
    },
    // HTTP/RMCP
    docs: {
      transport: 'http',
      url: 'https://mcp.internal/api',
      bearerTokenEnvVar: 'MCP_BEARER',
      httpHeaders: { 'x-tenant': 'acme' },
    },
  },
});
```

### Generic Config Overrides

- **`configOverrides`** (Record<string, string | number | boolean | object>): Generic Codex CLI config overrides. Allows setting any config value without updating the provider. Each entry maps to `-c <key>=<value>`.

Examples (nested objects are flattened to dotted keys):

```typescript
{
  experimental_resume: '/tmp/session.jsonl',           // string
  hide_agent_reasoning: true,                          // boolean
  model_context_window: 200000,                        // number
  sandbox_workspace_write: { network_access: true },   // object → -c sandbox_workspace_write.network_access=true
  'model_providers.custom.base_url': 'http://localhost:8000'  // nested config path
}
```

Values are serialized:

- string → raw string
- number/boolean → String(value)
- object → flattened to dotted keys (recursively)
- array → JSON.stringify(value)
- non-plain objects (Date, RegExp, Map, etc.) → JSON.stringify(value)

### Per-call Overrides (`providerOptions`, v0.4.0+)

Use AI SDK `providerOptions` to override Codex parameters for a single request without modifying the
model instance. The provider parses the `codex-cli` entry and applies the keys below:

- `reasoningEffort` → `model_reasoning_effort`
- `reasoningSummary` → `model_reasoning_summary`
- `reasoningSummaryFormat` → `model_reasoning_summary_format`
- `textVerbosity` → `model_verbosity` (AI SDK naming; mirrors constructor `modelVerbosity`)
- `addDirs` → appends `--add-dir` entries (merged with constructor `addDirs`)
- `configOverrides` → merged with constructor-level overrides (per-call values win on key conflicts)
- `mcpServers` → merged with constructor-level MCP servers (per-call values override per server)
- `rmcpClient` → overrides constructor `rmcpClient`

```ts
import { generateText } from 'ai';
import { codexCli } from 'ai-sdk-provider-codex-cli';

const model = codexCli('gpt-5.1', {
  reasoningEffort: 'medium',
  modelVerbosity: 'medium',
});

await generateText({
  model,
  prompt: 'Compare the trade-offs of high vs. low verbosity.',
  providerOptions: {
    'codex-cli': {
      reasoningEffort: 'high',
      reasoningSummary: 'detailed',
      textVerbosity: 'high',
      configOverrides: {
        'sandbox_workspace_write.network_access': true,
      },
    },
  },
});
```

**Precedence:** `providerOptions['codex-cli']` > constructor `CodexCliSettings` > Codex CLI defaults.

## Defaults & Recommendations

- Non‑interactive defaults:
  - `approvalMode: 'on-failure'`
  - `sandboxMode: 'workspace-write'`
  - `skipGitRepoCheck: true`
- For strict automation in controlled environments:
  - `fullAuto: true` OR `dangerouslyBypassApprovalsAndSandbox: true` (be careful!)

## Flag Mapping

### Core Settings

- `approvalMode` → `-c approval_policy=<mode>`
- `sandboxMode` → `-c sandbox_mode=<mode>`
- `skipGitRepoCheck` → `--skip-git-repo-check`
- `fullAuto` → `--full-auto`
- `dangerouslyBypassApprovalsAndSandbox` → `--dangerously-bypass-approvals-and-sandbox`
- `color` → `--color <always|never|auto>`
- `outputLastMessageFile` → `--output-last-message <path>`
- `addDirs` → `--add-dir <path>` (emitted once per entry)

### Model Parameters (v0.4.0+)

- `reasoningEffort` → `-c model_reasoning_effort=<value>`
- `reasoningSummary` → `-c model_reasoning_summary=<value>`
- `reasoningSummaryFormat` → `-c model_reasoning_summary_format=<value>`
- `modelVerbosity` → `-c model_verbosity=<value>`
- `includePlanTool` → `--include-plan-tool`
- `profile` → `--profile <name>`
- `oss` → `--oss`
- `webSearch` → `-c tools.web_search=true`
- `configOverrides` → `-c <key>=<value>` (for each entry)

### MCP

- `rmcpClient` → `-c features.rmcp_client=true`
- `mcpServers` → `-c mcp_servers.<name>.<field>=<value>` for each field (e.g., `command`, `args`, `env.KEY`, `url`, `bearer_token_env_var`, `http_headers.Header-Name`).

## JSON Mode (v0.2.0+)

When the AI SDK request uses `responseFormat: { type: 'json' }`, the provider:

1. Converts your Zod schema to JSON Schema format
2. Sanitizes the schema (removes unsupported fields like `format`, `pattern`, `$schema`, etc.)
3. Passes the schema via `--output-schema` for native OpenAI strict mode enforcement
4. The API returns guaranteed valid JSON matching your schema
5. AI SDK validates the response with Zod

**Breaking change from v0.1.x**: No longer uses prompt engineering. Schemas are enforced at the API level using OpenAI strict mode, which does not support optional fields or format validators.


--- docs/ai-sdk-v5/guide.md ---
# Codex CLI Provider – AI SDK v5 Guide

This guide explains how to use the Codex CLI provider with Vercel AI SDK v5 for text generation, streaming, and JSON object generation.

## Getting Started

1. Install Codex CLI and authenticate:

```bash
npm i -g @openai/codex
codex login   # or set OPENAI_API_KEY
```

2. Install AI SDK and this provider:

```bash
npm i ai ai-sdk-provider-codex-cli
```

## Basic Usage

```js
import { generateText, streamText, generateObject } from 'ai';
import { codexCli } from 'ai-sdk-provider-codex-cli';
import { z } from 'zod';

const model = codexCli('gpt-5.1-codex', {
  allowNpx: true,
  skipGitRepoCheck: true,
  approvalMode: 'on-failure',
  sandboxMode: 'workspace-write',
});

// Text
const { text } = await generateText({ model, prompt: 'Say hello in one word.' });

// Streaming
const { textStream } = await streamText({ model, prompt: 'Two short lines.' });
for await (const chunk of textStream) process.stdout.write(chunk);

// Object (JSON)
const schema = z.object({ name: z.string(), age: z.number().int() });
const { object } = await generateObject({ model, schema, prompt: 'Generate a user.' });
```

## Conversation History

Use AI SDK messages to retain context:

```js
const messages = [
  { role: 'user', content: 'My name is Dana.' },
  { role: 'assistant', content: 'Hi Dana!' },
  { role: 'user', content: 'What did I just tell you my name was?' },
];
const { text } = await generateText({ model, messages });
```

## Structured Output (JSON)

**v0.2.0+**: The provider uses native `--output-schema` support with OpenAI strict mode for API-level JSON enforcement. Schemas are passed directly to the API, eliminating 100-200 tokens per request and improving reliability.

**⚠️ Important Limitations:**

- Optional fields are **NOT supported** by OpenAI strict mode (all fields must be required)
- Format validators (`.email()`, `.url()`, `.uuid()`) are stripped (use descriptions instead)
- Pattern validators (`.regex()`) are stripped (use descriptions instead)

See [LIMITATIONS.md](../../LIMITATIONS.md) for full details.

Tips:

- Add clear field descriptions to your Zod schema (especially for format hints like "UUID format", "YYYY-MM-DD date")
- All fields must be required (no `.optional()`)
- Use descriptions instead of format validators
- Keep constraints realistic for better adherence

## Permissions & Sandbox

The provider applies safe defaults for non‑interactive execution. You can override them per call via provider settings:

- `fullAuto: true` → `--full-auto`
- `dangerouslyBypassApprovalsAndSandbox: true` → `--dangerously-bypass-approvals-and-sandbox`
- Otherwise, the provider writes config overrides: `-c approval_policy=...` and `-c sandbox_mode=...`.

Recommended defaults for CI/local automation:

- `approvalMode: 'on-failure'`
- `sandboxMode: 'workspace-write'`
- `skipGitRepoCheck: true`

## Streaming Behavior

**Status:** Incremental streaming not currently supported with `--experimental-json` format (expected in future Codex CLI releases)

The `--experimental-json` output format (introduced Sept 25, 2025) currently only emits `item.completed` events with full text content. Incremental streaming via `item.updated` or delta events is not yet implemented by OpenAI.

**What this means:**

- `streamText()` works functionally but delivers the entire response in a single chunk after generation completes
- No incremental text deltas—you wait for the full response, then receive it all at once
- The AI SDK's streaming interface is supported, but actual incremental streaming is not available

**How the provider handles this:**

1. Emits `response-metadata` stream part when the session is configured
2. Waits for `item.completed` event with the final assistant message
3. Emits a single `text-delta` with the full text
4. Emits `finish`

**Future support:** The Codex CLI commit (344d4a1d) introducing experimental JSON explicitly notes: "or other item types like `item.output_delta` when we need streaming" and states "more event types and item types to come."

When OpenAI adds streaming support, this provider will be updated to handle those events and enable true incremental streaming. Your code using the AI SDK stream API will remain compatible.

## Logging Configuration

Control how the provider logs execution information, warnings, and errors. The logger supports multiple log levels and a verbose mode for detailed debugging.

### Log Levels

The provider supports four log levels:

- **`debug`**: Detailed execution tracing (request/response, tool calls, stream events)
- **`info`**: General execution flow information (session initialization, completion)
- **`warn`**: Warnings about configuration issues or unexpected behavior
- **`error`**: Error messages for failures and exceptions

### Basic Configuration

```typescript
import { createCodexCli } from 'ai-sdk-provider-codex-cli';

// Default: logs warnings and errors to console
const defaultCodex = createCodexCli();

// Disable all logging
const silentCodex = createCodexCli({
  defaultSettings: {
    logger: false,
  },
});

// Custom logger - must implement all four log levels
const customCodex = createCodexCli({
  defaultSettings: {
    logger: {
      debug: (message) => myLogger.debug('Codex:', message),
      info: (message) => myLogger.info('Codex:', message),
      warn: (message) => myLogger.warn('Codex:', message),
      error: (message) => myLogger.error('Codex:', message),
    },
  },
});

// Model-specific logger override
const model = customCodex('gpt-5.1-codex', {
  logger: false, // Disable logging for this model only
});
```

### Verbose Mode (Debug Logging)

Enable verbose mode to see detailed execution logs, including:

- Request/response tracing
- Tool execution lifecycle (tool calls, results, errors)
- Stream event processing
- Command execution details and token usage
- Session management

**Without verbose mode**, only `warn` and `error` messages are logged.
**With verbose mode enabled**, `debug` and `info` messages are also logged.

```typescript
import { createCodexCli } from 'ai-sdk-provider-codex-cli';

// Enable verbose logging for debugging
const codexWithDebug = createCodexCli({
  defaultSettings: {
    verbose: true, // Enable debug and info logging
  },
});

// Use with custom logger
const codexCustom = createCodexCli({
  defaultSettings: {
    verbose: true,
    logger: {
      debug: (msg) => console.log(`[DEBUG] ${msg}`),
      info: (msg) => console.log(`[INFO] ${msg}`),
      warn: (msg) => console.warn(`[WARN] ${msg}`),
      error: (msg) => console.error(`[ERROR] ${msg}`),
    },
  },
});

// Model-specific verbose override
const model = codexWithDebug('gpt-5.1-codex', {
  verbose: false, // Disable verbose for this specific model
});
```

### What Gets Logged in Verbose Mode

With `verbose: true`, you'll see intermediate process logs including:

**For `generateText()` calls:**

```
[DEBUG] [codex-cli] Starting doGenerate request with model: gpt-5.1-codex
[DEBUG] [codex-cli] Request mode: regular, response format: none
[DEBUG] [codex-cli] Converted 2 messages, response format: none
[DEBUG] [codex-cli] Executing Codex CLI: npx with 15 arguments, cwd: default
[DEBUG] [codex-cli] Received event type: thread.started
[DEBUG] [codex-cli] Session started: thread-abc123
[DEBUG] [codex-cli] Received event type: turn.completed
[INFO] [codex-cli] Request completed - Session: thread-abc123, Duration: 1523ms, Tokens: 373
[DEBUG] [codex-cli] Token usage - Input: 245, Output: 128, Total: 373
```

**For `streamText()` calls with tools:**

```
[DEBUG] [codex-cli] Starting doStream request with model: gpt-5.1-codex
[DEBUG] [codex-cli] Converted 1 messages for streaming, response format: none
[DEBUG] [codex-cli] Executing Codex CLI for streaming: npx with 14 arguments
[DEBUG] [codex-cli] Stream event: thread.started
[DEBUG] [codex-cli] Stream session started: thread-xyz789
[DEBUG] [codex-cli] Stream event: item.started
[DEBUG] [codex-cli] Tool detected: exec, item type: command_execution
[DEBUG] [codex-cli] Emitting tool invocation: exec
[DEBUG] [codex-cli] Stream event: item.completed
[DEBUG] [codex-cli] Tool completed: exec
[DEBUG] [codex-cli] Received assistant message, length: 142
[INFO] [codex-cli] Stream completed - Session: thread-xyz789, Duration: 3241ms, Tokens: 768
[DEBUG] [codex-cli] Token usage - Input: 512, Output: 256, Total: 768
```

### Logger Options

- `undefined` (default): Uses `console.debug`, `console.info`, `console.warn`, and `console.error`
- `false`: Disables all logging
- Custom `Logger` object: Must implement `debug`, `info`, `warn`, and `error` methods

### Combining with Error Handling

For comprehensive debugging, combine verbose logging with error handling:

```typescript
import { createCodexCli } from 'ai-sdk-provider-codex-cli';
import { generateText } from 'ai';

const codexCli = createCodexCli({
  defaultSettings: {
    verbose: true,
    logger: {
      debug: (msg) => myLogger.debug(msg),
      info: (msg) => myLogger.info(msg),
      warn: (msg) => myLogger.warn(msg),
      error: (msg) => myLogger.error(msg),
    },
  },
});

try {
  const result = await generateText({
    model: codexCli('gpt-5.1-codex'),
    prompt: 'Hello!',
  });
} catch (error) {
  console.error('Generation failed:', error);
  // Check error.data for additional context (exitCode, stderr, etc.)
  if (error.data) {
    console.error('Error details:', error.data);
  }
}
```

## Examples

See `examples/` for runnable scripts that cover:

- Basic text generation and streaming
- Conversation history and system messages
- Permissions & sandbox modes
- JSON object generation: basic, nested, constraints, advanced


## Links discovered
- [LIMITATIONS.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/../../LIMITATIONS.md)

--- docs/ai-sdk-v5/limitations.md ---
# Limitations

## Runtime & Architecture

- Node.js runtime only (spawns a local process); Edge runtimes are not supported.
- Image inputs are not supported.

## Streaming Behavior

- Codex `--experimental-json` mode emits events (`thread.started`, `turn.completed`, `item.completed`) rather than streaming text deltas; streaming usually returns a final chunk. The CLI provides the final assistant content in the `item.completed` event, which this provider reads and emits at the end.

## Tool Streaming (v0.3.0+)

- Tool streaming is fully supported - tool invocation and result events are emitted in real-time
- **Limitation:** Real-time output streaming (`output-delta` events) not yet available. Tool outputs are delivered in the final `tool-result` event via `aggregatedOutput` field, not as incremental deltas during tool execution
- This limitation exists because Codex CLI's experimental JSON format doesn't currently emit incremental output events during tool execution

## JSON Schema (v0.2.0+)

- **Optional fields NOT supported**: OpenAI strict mode requires all fields to be required (no `.optional()`)
- **Format validators stripped**: `.email()`, `.url()`, `.uuid()` are removed during sanitization (use descriptions instead)
- **Pattern validators stripped**: `.regex()` is removed during sanitization (use descriptions instead)
- See [LIMITATIONS.md](../../LIMITATIONS.md) at repo root for comprehensive details

## AI SDK Parameter Support

- Some AI SDK parameters are not applicable to Codex CLI (e.g., temperature, topP, penalties). The provider surfaces warnings and ignores them.

## Model Parameter Validation (v0.4.0+)

**Known API Quirks:**

### reasoningSummary Parameter

The OpenAI Responses API has misleading error messages for the `reasoningSummary` parameter:

- **Valid values:** `'auto'`, `'detailed'`
- **Invalid values:** `'concise'`, `'none'` (rejected with 400 errors)

**The quirk:** When you pass an invalid value like `'none'`, the API error claims valid values are `'concise', 'detailed', and 'auto'`. However, if you then try `'concise'`, the API rejects it with: `"Unsupported value: 'concise' is not supported with the 'gpt-5.1-codex' model."`

This provider's type system and validation only allow `'auto'` and `'detailed'` to prevent runtime errors.

## Observability

- Token usage tracking is available via `turn.completed` events (requires Codex CLI >= 0.44.0)
- Earlier versions (< 0.44.0) will report 0 for all token counts


## Links discovered
- [LIMITATIONS.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/../../LIMITATIONS.md)

--- docs/ai-sdk-v5/migration-0.2.md ---
# Migration Guide: v0.1.x → v0.2.0

This guide helps you migrate from v0.1.x to v0.2.0 of the AI SDK Provider for Codex CLI.

## Overview

Version 0.2.0 introduces **breaking changes** that significantly improve reliability and token efficiency by leveraging Codex CLI's native JSON schema support.

## Breaking Changes

### 1. Native Schema Support Only

**What changed:**

- Removed prompt engineering for JSON generation
- Removed `extract-json.ts` extraction logic
- All JSON output now uses `--output-schema` (API-enforced)

**Impact:**

- ✅ **Better**: 100-200 fewer tokens per JSON request
- ✅ **Better**: More reliable structured output (API-level enforcement)
- ✅ **Better**: No more manual JSON extraction

**Migration steps:**

- ⚠️ **IMPORTANT**: Check all Zod schemas for unsupported features (see below)
- Remove any manual JSON instructions you added to prompts (they're redundant now)

### OpenAI Strict Mode Limitations

**What changed:**

- OpenAI strict mode does NOT support optional fields
- Format validators (`.email()`, `.url()`, `.uuid()`) are stripped
- Pattern validators (`.regex()`) are stripped

**Impact:**

- ⚠️ **Breaking**: Schemas with `.optional()` will fail
- ⚠️ **Breaking**: Format validators are ignored (no validation)
- ⚠️ **Breaking**: Pattern validators are ignored (no validation)

**Migration steps:**

1. Remove all `.optional()` calls - make fields required or use empty string/null defaults
2. Replace format validators with descriptions:
   - `.email()` → `.describe('Valid email address')`
   - `.url()` → `.describe('Valid URL')`
   - `.uuid()` → `.describe('UUID format')`
3. Replace pattern validators with descriptions:
   - `.regex(/^\d{4}-\d{2}-\d{2}$/)` → `.describe('Date in YYYY-MM-DD format')`

**Example:**

```javascript
// ❌ Before (v0.1.x) - WILL FAIL in v0.2.0
const schema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  website: z.string().url().optional(),
  date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
});

// ✅ After (v0.2.0) - Works with OpenAI strict mode
const schema = z.object({
  id: z.string().describe('UUID format'),
  email: z.string().describe('Valid email address'),
  website: z.string().describe('Personal website URL (or empty string if none)'),
  date: z.string().describe('Date in YYYY-MM-DD format'),
});
```

See [LIMITATIONS.md](../../LIMITATIONS.md) for comprehensive details.

### 2. New Event Format

**What changed:**

- Switched from `--json` to `--experimental-json`
- Event structure changed from old format

**Old format (v0.1.x):**

```json
{
  "id": "evt_123",
  "msg": {
    "type": "session_configured",
    "session_id": "abc123"
  }
}
```

**New format (v0.2.0+):**

```json
{
  "type": "session.created",
  "session_id": "abc123"
}
```

**Impact:**

- ✅ **Better**: Structured event types
- ✅ **Better**: Usage tracking from `turn.completed` events
- ⚠️ **Breaking**: Event structure is different (handled internally by provider)

**Migration steps:**

- No action needed if you're using the high-level AI SDK APIs (`generateText`, `generateObject`, etc.)
- If you were parsing raw events, update to new format

### 3. Simplified Internal API

**What changed:**

- Removed `mode` parameter from `mapMessagesToPrompt`
- Removed `jsonSchema` parameter from `mapMessagesToPrompt`
- Removed `PromptMode` type

**Impact:**

- ⚠️ **Breaking**: If you were importing and using `mapMessagesToPrompt` directly
- ✅ **Better**: Simpler internal API

**Migration steps:**

- If you weren't using internal APIs: no action needed
- If you were: update to simplified signature (just pass `prompt`)

## Benefits of v0.2.0

### Token Efficiency

**Before (v0.1.x):**

```
Prompt: CRITICAL: You MUST respond with ONLY a JSON object. NO other text.
Your response MUST start with { and end with }
The JSON MUST match this EXACT schema:
{
  "type": "object",
  "properties": { ... }
}

Now, based on the following conversation, generate ONLY the JSON object:

[Your actual prompt here]
```

**Cost:** ~150 extra tokens

**After (v0.2.0):**

```
[Your actual prompt here]
```

**Cost:** 0 extra tokens (schema passed via `--output-schema`)

### Reliability

**Before (v0.1.x):**

- Prompt engineering → model sometimes adds text outside JSON
- Manual extraction → brittle brace-counting logic
- No API-level enforcement → model can deviate from schema

**After (v0.2.0):**

- Native schema → API enforces with `strict: true`
- No extraction needed → guaranteed valid JSON
- Model cannot deviate from schema

### Code Simplicity

**Removed:**

- ~50 lines of prompt engineering logic
- ~20 lines of JSON extraction logic
- Multiple code paths for different modes

**Result:**

- Simpler, more maintainable codebase
- Fewer edge cases
- Better error handling

## Migration Checklist

### For Most Users

- [ ] Update package: `npm install ai-sdk-provider-codex-cli@0.2.0`
- [ ] **CRITICAL**: Review ALL Zod schemas:
  - [ ] Remove all `.optional()` calls
  - [ ] Replace `.email()`, `.url()`, `.uuid()` with `.describe()`
  - [ ] Replace `.regex()` with `.describe()`
- [ ] Test your existing `generateObject` calls
- [ ] Review prompts and remove any manual JSON instructions
- [ ] Verify output matches expectations
- [ ] Celebrate improved token efficiency! 🎉

### For Advanced Users (using internal APIs)

- [ ] Update `mapMessagesToPrompt` calls if you were using it directly
- [ ] Update event parsing if you were parsing raw CLI events
- [ ] Review custom integrations

### For Library Authors

- [ ] Update to v0.2.0 in your dependencies
- [ ] Test integration with new event format
- [ ] Update documentation to reflect changes

## Example: Before & After

### Before (v0.1.x)

```javascript
import { generateObject } from 'ai';
import { codexCli } from 'ai-sdk-provider-codex-cli';
import { z } from 'zod';

const schema = z.object({
  name: z.string(),
  age: z.number().int(),
});

// Provider adds ~150 tokens of JSON instructions to prompt
const { object } = await generateObject({
  model: codexCli('gpt-5'),
  schema,
  prompt: 'Generate a person profile',
});
```

### After (v0.2.0)

```javascript
import { generateObject } from 'ai';
import { codexCli } from 'ai-sdk-provider-codex-cli';
import { z } from 'zod';

const schema = z.object({
  name: z.string(),
  age: z.number().int(),
});

// Provider passes schema via --output-schema (0 extra tokens!)
const { object } = await generateObject({
  model: codexCli('gpt-5'),
  schema,
  prompt: 'Generate a person profile',
});
```

**Result:** Same API, same code, but:

- 150 fewer tokens per request
- More reliable output
- Faster responses

## Troubleshooting

### Issue: "Invalid JSON" errors

**Cause:** Shouldn't happen with native schema enforcement

**Solution:**

1. Verify you're on v0.2.0: `npm list ai-sdk-provider-codex-cli`
2. Check Codex CLI version: `codex --version` (should support `--output-schema`)
3. Report issue with example if problem persists

### Issue: Different output format than expected

**Cause:** Schema validation is now stricter (API-level enforcement)

**Solution:**

1. Review your Zod schema
2. Check for required fields
3. Verify enum values match expectations
4. Use examples to test schema design

### Issue: "Unknown flag: --output-schema"

**Cause:** Your Codex CLI version is too old

**Solution:**

1. Update Codex CLI: `npm install -g @openai/codex@latest`
2. Or use Homebrew: `brew upgrade codex`
3. Verify: `codex --version`

### Issue: Missing usage stats

**Cause:** Usage stats come from `turn.completed` events in experimental JSON format

**Solution:**

1. Verify the provider is using `--experimental-json` (automatic in v0.2.0)
2. Check that Codex CLI is returning usage in events
3. Enable debug logging if needed

## Getting Help

- 📖 Read the [full documentation](./guide.md)
- 🐛 Report issues on [GitHub](https://github.com/ben-vargas/ai-sdk-provider-codex-cli/issues)
- 💬 Ask questions in discussions
- 📝 Check the [CHANGELOG](../../CHANGELOG.md) for detailed changes

## What's Next?

With v0.2.0 stable, future improvements may include:

- Better streaming support when Codex CLI adds it
- More granular event tracking
- Performance optimizations
- Additional configuration options

Stay tuned for updates!


## Links discovered
- [LIMITATIONS.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/../../LIMITATIONS.md)
- [full documentation](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/./guide.md)
- [GitHub](https://github.com/ben-vargas/ai-sdk-provider-codex-cli/issues)
- [CHANGELOG](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/../../CHANGELOG.md)

--- docs/ai-sdk-v5/troubleshooting.md ---
# Troubleshooting

## "codex not found" / CLI not on PATH

- Install globally: `npm i -g @openai/codex`
- Or enable fallback: `{ allowNpx: true }` (uses `npx -y @openai/codex`)

## Not authenticated / 401 / "Please login"

- Run `codex login`
- Ensure `~/.codex/auth.json` exists and is readable
- Alternatively set `OPENAI_API_KEY` in `env`

## Sandbox / approval errors

- Use safer defaults for non‑interactive runs:
  - `approvalMode: 'on-failure'`
  - `sandboxMode: 'workspace-write'`
  - `skipGitRepoCheck: true`
- For fully autonomous flows: `fullAuto: true` (be cautious). Avoid `dangerouslyBypassApprovalsAndSandbox` unless the environment is already sandboxed.

## Streaming emits only a final chunk

- Codex `--experimental-json` mode emits events (`session.created`, `turn.completed`, `item.completed`) rather than streaming text deltas; the provider still uses AI SDK's standard stream API. This is expected.

## Object generation fails with empty response

**v0.2.0+**: The provider uses native `--output-schema` with OpenAI strict mode. Common issues:

- **Optional fields**: Remove all `.optional()` calls - OpenAI strict mode requires all fields
- **Format validators**: Remove `.email()`, `.url()`, `.uuid()` - use descriptions like "Valid email address" or "UUID format" instead
- **Pattern validators**: Remove `.regex()` - use descriptions like "YYYY-MM-DD format" instead

See [LIMITATIONS.md](../../LIMITATIONS.md) for full details.

## zod v3/v4 compatibility warnings

- NPM warnings may appear due to transitive peers (e.g., `zod-to-json-schema`). They do not affect functionality. The provider works with `zod@^3` and `^4`.


## Links discovered
- [LIMITATIONS.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/../../LIMITATIONS.md)

--- examples/README.md ---
# Codex CLI Provider Examples

This folder showcases how to use the AI SDK Codex CLI provider in practical scenarios. Each example is small, focused, and explains why it matters.

## Prerequisites

- Install and authenticate the Codex CLI:
  - `npm i -g @openai/codex`
  - `codex login` (ChatGPT OAuth) or set `OPENAI_API_KEY` for API auth
- Build the provider: `npm run build`

Tip: All examples set `allowNpx: true`, so they work even if `codex` is not on PATH. The provider is Node-only (it spawns a process), so run these in a Node environment (not Edge).

## How To Run

Run any example from the repo root:

```bash
npm run build
node examples/<file>.mjs
```

## Core Usage

- **basic-usage.mjs:** Minimal generation
  - Purpose: Prove setup works and show the smallest possible call.
  - Demonstrates: `generateText`, provider wiring, safe defaults.
  - Value: Quick sanity check to confirm your environment is correct.

- **basic-usage-gpt-5.1-codex.mjs:** Minimal generation with the Codex-optimized GPT-5.1 slug
  - Purpose: Confirm the provider works unchanged with the Codex-specific GPT-5.1 model ID.
  - Demonstrates: Same call path as above, but with the Codex slug so you can sanity check quickly.
  - Value: Handy regression test when Codex CLI ships new model identifiers.

- **streaming.mjs:** Stream responses
  - Purpose: Show the AI SDK streaming API shape.
  - Demonstrates: Reading `textStream` and rendering as chunks.
  - Value: Build responsive UIs. **Note:** `--experimental-json` format currently doesn't support incremental streaming—you'll receive the full response in a single chunk. The streaming API pattern is correct and will work when OpenAI adds delta event support to Codex CLI.

- **streaming-gpt-5.1-codex.mjs:** Streaming with the `gpt-5.1-codex` slug
  - Purpose: Validate stream handling with the Codex-specific model identifier.
  - Demonstrates: Same stream plumbing while calling the Codex slug.
  - Value: Confidence that streaming stays compatible across Codex model updates. **Note:** Currently delivers full response in single chunk due to experimental JSON format limitations.

- **conversation-history.mjs:** Maintain context
  - Purpose: Keep multi-turn state using a message array.
  - Demonstrates: AI SDK message roles (`user`, `assistant`).
  - Value: Realistic chat patterns where prior turns matter.

- **system-messages.mjs:** Control behavior
  - Purpose: Use system prompts to steer tone or format.
  - Demonstrates: `system` role to enforce concise or structured replies.
  - Value: Consistency across outputs without repeating instructions.

- **system-messages-gpt-5.1-codex.mjs:** System prompts with `gpt-5.1-codex`
  - Purpose: Mirror the system prompt example against the Codex slug to ensure compatibility.
  - Demonstrates: That the conversation mapper/system validation still behaves the same.
  - Value: Fast compatibility regression check for future Codex CLI updates.

- **custom-config.mjs:** Configure runtime
  - Purpose: Customize CWD and autonomy/sandbox policies per run.
  - Demonstrates: `cwd`, `approvalMode`, `sandboxMode`, `fullAuto` toggles.
  - Value: Balance safety vs. friction for local dev or CI use.

- **permissions-and-sandbox.mjs:** Compare modes
  - Purpose: Understand autonomy levels and sandbox modes.
  - Demonstrates: `on-failure`, `workspace-write`, `fullAuto`, and `dangerouslyBypassApprovalsAndSandbox`.
  - Value: Pick the right guardrails for your workflow. Warning: bypass is dangerous; prefer sandboxed modes unless you fully trust the environment.

- **advanced-settings.mjs:** Constructor-level model parameters (v0.4.0+)
  - Purpose: Demonstrate comprehensive reasoning controls and advanced Codex features at model creation.
  - Demonstrates: `reasoningEffort`, `reasoningSummary`, `webSearch`, `includePlanTool`, `profile`, `mcpServers`, `rmcpClient`, and `configOverrides`. (Swap to the non-Codex `gpt-5.1` slug if you need `modelVerbosity`.)
  - Value: See all Phase 1 parameters in action—configure behavior once at construction for consistent settings across all calls.

- **provider-options.mjs:** Per-call overrides (v0.4.0+)
  - Purpose: Show how to override reasoning and config settings for individual requests.
  - Demonstrates: `providerOptions['codex-cli']` with `reasoningEffort`, `reasoningSummary`, `textVerbosity`, `mcpServers`, `rmcpClient`, and `configOverrides`.
  - Value: Tune behavior dynamically (e.g., low-effort quick checks vs. high-effort deep dives) without cloning model instances.

## Reliability & Operations

- **long-running-tasks.mjs:** Abort and timeouts
  - Purpose: Cancel long operations cleanly.
  - Demonstrates: `AbortController` with AI SDK calls.
  - Value: Keep apps responsive and prevent runaway tasks.

- **error-handling.mjs:** Catch and classify errors
  - Purpose: Handle auth and general failures gracefully.
  - Demonstrates: Using `isAuthenticationError`, reading provider warnings.
  - Value: User-friendly errors (e.g., suggest `codex login`) and robust UX.

- **check-cli.mjs:** Troubleshoot setup
  - Purpose: Verify Codex binary and authentication status.
  - Demonstrates: Calling `codex --version` and `codex login status` (or `npx`).
  - Value: Quick diagnosis for PATH/auth issues.

- **limitations.mjs:** Understand unsupported settings
  - Purpose: Show which AI SDK knobs are ignored by Codex.
  - Demonstrates: Warnings for temperature/topP/topK/penalties/stop sequences.
  - Value: Avoid confusion and tune your prompts instead.

## Logging

- **logging-default.mjs:** Default logging behavior
  - Purpose: Show the default non-verbose logging mode.
  - Demonstrates: Only warn and error messages are logged, debug/info suppressed.
  - Value: Clean output for production—only essential logs appear.

- **logging-verbose.mjs:** Verbose mode for debugging
  - Purpose: Enable detailed execution logs for troubleshooting.
  - Demonstrates: All log levels (debug, info, warn, error) with full visibility.
  - Value: Development and debugging—see exactly what the provider is doing internally.

- **logging-custom-logger.mjs:** Custom logger integration
  - Purpose: Integrate with external logging systems (Winston, Pino, Datadog, etc.).
  - Demonstrates: Custom logger object with timestamps and prefixes.
  - Value: Route logs to your observability stack, format messages your way.

- **logging-disabled.mjs:** Silent operation
  - Purpose: Completely disable all provider logging.
  - Demonstrates: Setting `logger: false` for zero log output.
  - Value: Production scenarios where logs interfere with output processing.
  - Warning: No warnings or errors from the provider will be visible!

## Structured Output (Objects)

**v0.2.0+**: The provider uses native `--output-schema` support with OpenAI strict mode for API-level JSON enforcement. No prompt engineering needed—schemas are passed directly to the API, eliminating 100-200 tokens per request and improving reliability.

**⚠️ Important Limitations:**

- Optional fields are **NOT supported** by OpenAI strict mode (all fields must be required)
- Format validators (`.email()`, `.url()`, `.uuid()`) are stripped (use descriptions instead)
- Pattern validators (`.regex()`) are stripped (use descriptions instead)

See [LIMITATIONS.md](../LIMITATIONS.md) for full details.

- **generate-object-basic.mjs:** Fundamentals
  - Purpose: Start with simple, typed objects.
  - Demonstrates: Zod primitives, arrays, and numeric constraints.
  - Value: Cleanly typed responses for standard data collection.
  - Note: All fields must be required (no `.optional()`).

- **generate-object-basic-gpt-5.1-codex.mjs:** Fundamentals with `gpt-5.1-codex`
  - Purpose: Exercise JSON object generation against the Codex slug.
  - Demonstrates: Same Zod-driven prompts, proving compatibility with new identifiers.
  - Value: Quick regression path when Codex CLI ships new GPT-5 model slugs.

- **generate-object-nested.mjs:** Real-world hierarchies
  - Purpose: Work with nested objects and arrays of objects.
  - Demonstrates: Organization charts, product variants, nested specs.
  - Value: Match the shape of real app payloads and APIs.

- **generate-object-constraints.mjs:** Quality and validation
  - Purpose: Enforce enums, ranges, and constraints.
  - Demonstrates: Enums, min/max numeric constraints, string length constraints.
  - Value: Higher-quality data before it enters your system.
  - Note: Use descriptions for format hints (e.g., "UUID format", "YYYY-MM-DD date") since format/pattern validators are stripped.

- **generate-object-advanced.mjs:** Complex transformations
  - Purpose: Tackle richer tasks and data extraction.
  - Demonstrates: Product comparisons with scoring, HTML-to-JSON extraction, incident classification with recommendations.
  - Value: Turn free-form inputs into structured, actionable data.

- **generate-object-native-schema.mjs:** Native schema showcase (v0.2.0+)
  - Purpose: Demonstrate native `--output-schema` capabilities with API-level enforcement.
  - Demonstrates: Complex nested schemas, enums, constraints enforced by OpenAI strict mode.
  - Value: See the power of native schema support—no prompt engineering, 100-200 fewer tokens per request, guaranteed valid JSON.

## New in v0.2.0

- **experimental-json-events.mjs:** Event format showcase
  - Purpose: Understand the new `--experimental-json` event structure.
  - Demonstrates: `thread.started`, `turn.completed`, `item.completed` events, usage tracking.
  - Value: Learn the event flow for debugging and observability.

## Tool Streaming

**Note:** Codex CLI executes tools autonomously, so the provider sets `providerExecuted: true` on all tool calls. This means the AI SDK will not attempt to execute tools—it simply receives the results from Codex CLI.

**⚠️ Streaming Limitation:** Real-time output streaming (`output-delta` events) is not yet available. Tool outputs are delivered in the final `tool-result` event via the `aggregatedOutput` field. The provider correctly implements the AI SDK tool streaming API, but incremental stdout/stderr streaming will require additional support in Codex CLI's event format.

- **streaming-tool-calls.mjs:** Basic tool streaming
  - Purpose: Demonstrate tool streaming API with Codex CLI tool execution.
  - Demonstrates: `tool-input-start`, `tool-input-delta`, `tool-input-end`, `tool-call`, `tool-result` events for exec commands.
  - Value: See how tool invocation and results flow through the AI SDK streaming interface. Monitor what tools Codex CLI executes in real time.
  - Note: Tool outputs appear in final result, not as streaming deltas (see limitation above).

- **streaming-multiple-tools.mjs:** Multiple sequential tool calls
  - Purpose: Show complex multi-tool workflows with result tracking.
  - Demonstrates: Sequential tool execution, abbreviated output display, tool call numbering.
  - Value: Build UIs that track progress across multiple tool invocations. Great for debugging complex agent workflows.
  - Note: Shows tool inputs immediately and outputs when completed (aggregated, not streaming).

## Suggested Run Order

1. `basic-usage.mjs` → `streaming.mjs` → `conversation-history.mjs`
2. `custom-config.mjs` → `permissions-and-sandbox.mjs` → `advanced-settings.mjs` → `provider-options.mjs` (v0.4.0 features)
3. `logging-default.mjs` → `logging-verbose.mjs` → `logging-custom-logger.mjs` → `logging-disabled.mjs` (logging)
4. `generate-object-basic.mjs` → `generate-object-nested.mjs` → `generate-object-constraints.mjs` → `generate-object-advanced.mjs` → `generate-object-native-schema.mjs`
5. `experimental-json-events.mjs` (v0.2.0 event format)
6. `streaming-tool-calls.mjs` → `streaming-multiple-tools.mjs` (tool streaming)
7. `long-running-tasks.mjs` → `error-handling.mjs` → `limitations.mjs` → `check-cli.mjs`

## Troubleshooting

- Not getting output? Run `node examples/check-cli.mjs`.
- Auth failures? Run `codex login` or set `OPENAI_API_KEY`.
- PATH issues? Keep `allowNpx: true` or install `@openai/codex` globally.
- Streaming not incremental? The `--experimental-json` format (introduced Sept 25, 2025) doesn't yet support streaming deltas—you'll receive the full response in a single chunk. This is expected behavior until OpenAI adds delta event support. The streaming API pattern remains correct for future compatibility.


## Links discovered
- [LIMITATIONS.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/examples/../LIMITATIONS.md)

--- CHANGELOG.md ---
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.6.0] - 2025-11-21

### Added

- **First-class MCP configuration**: `mcpServers` and `rmcpClient` settings map directly to Codex CLI MCP config keys (stdio + HTTP/RMCP), with per-call overrides, validation, and tests.
- **Documentation and examples**: README, docs, and examples now show MCP server setup without relying on `configOverrides`.
- **Add-dirs support**: New `addDirs` setting (array of strings) to expose additional directories to the model context (maps to `--add-dir`).

### Fixed

- **File Preservation**: The `outputLastMessageFile` is no longer deleted after execution if the user explicitly provided the path. Auto-generated temp files are still cleaned up.

## [0.5.0] - 2025-10-21

### Added

- **Comprehensive logging system** with configurable verbosity and custom logger support
  - Added `debug` and `info` log levels to complement existing `warn` and `error` levels
  - New `verbose` setting to control debug/info logging visibility (default: `false` for clean production output)
  - New `logger` setting for custom logger support or `false` to disable all logging
  - `Logger` interface: Standardized four-level logging (debug, info, warn, error)
  - Default logger with level tags: `[DEBUG]`, `[INFO]`, `[WARN]`, `[ERROR]` prefixes
  - Detailed execution tracing including request/response flow, stream events, and process lifecycle
  - When `verbose: false` (default), only `warn` and `error` messages are logged
  - When `verbose: true`, all log levels including `debug` and `info` are logged
  - `createVerboseLogger()` utility that filters debug/info logs based on verbose mode
  - `this` context preservation via `.bind()` for class-based custom loggers
- **Logging examples:**
  - `examples/logging-default.mjs`: Default non-verbose mode (warn/error only)
  - `examples/logging-verbose.mjs`: Verbose mode with full debug visibility
  - `examples/logging-custom-logger.mjs`: Custom logger integration (Winston, Pino, etc.)
  - `examples/logging-disabled.mjs`: Complete logging suppression
- **Documentation:**
  - `docs/ai-sdk-v5/guide.md`: Comprehensive logging configuration section
  - `docs/ai-sdk-v5/configuration.md`: Detailed `verbose` and `logger` parameter documentation
  - `examples/README.md`: Logging examples section with usage patterns

### Potentially Breaking Changes

**Who is affected:** Only users with custom `Logger` implementations (estimated <5% of users).

**What changed:** The `Logger` interface now requires 4 methods instead of 2:

- `debug(message: string): void` - NEW - for detailed execution tracing (verbose mode only)
- `info(message: string): void` - NEW - for general flow information (verbose mode only)
- `warn(message: string): void` - existing
- `error(message: string): void` - existing

**Migration for custom logger users:**

```typescript
// Before (v0.4.x) ❌
const logger = {
  warn: (msg) => myLogger.warn(msg),
  error: (msg) => myLogger.error(msg),
};

// After (v0.5.0+) ✅
const logger = {
  debug: (msg) => myLogger.debug(msg), // Add this
  info: (msg) => myLogger.info(msg), // Add this
  warn: (msg) => myLogger.warn(msg),
  error: (msg) => myLogger.error(msg),
};
```

**Most users are unaffected:**

- Users without a custom logger (using default `console`) - no changes needed
- Users with `logger: false` - no changes needed
- The default logger automatically handles all log levels

### Changed

- **Default logger now includes level tags** - All log messages are prefixed with `[DEBUG]`, `[INFO]`, `[WARN]`, or `[ERROR]` for clarity
  - May affect applications parsing console output (use custom logger or `logger: false` if needed)
- Non-verbose mode (default) only shows warn/error messages for cleaner production logs

### Technical Details

- All new settings (`verbose`, `logger`) are optional with safe defaults
- 7 new unit tests covering logger functionality (all passing)
- Comprehensive test coverage for all logging scenarios and custom logger implementations
- Supports custom logging integrations (Winston, Pino, Datadog, Sentry, etc.)

## [0.4.0] - 2025-10-06

### Added

- **Constructor-level model parameters:**
  - `reasoningEffort`: Control reasoning depth for o3, o4-mini, gpt-5, gpt-5-codex ('minimal' | 'low' | 'medium' | 'high')
  - `reasoningSummary`: Control reasoning summary detail level ('auto' | 'detailed')
    - Note: Only 'auto' and 'detailed' are supported despite API error messages claiming otherwise
  - `reasoningSummaryFormat`: Experimental format control ('none' | 'experimental')
  - `modelVerbosity`: GPT-5 family output length control ('low' | 'medium' | 'high')
- **Advanced Codex features:**
  - `includePlanTool`: Enable experimental plan tool (`--include-plan-tool`)
  - `profile`: Load config profile from `~/.codex/config.toml` (`--profile <name>`)
  - `oss`: Use OSS provider (`--oss`)
  - `webSearch`: Enable web search tool (`-c tools.web_search=true`)
- **Generic config overrides:**
  - `configOverrides`: Ultimate flexibility - set ANY Codex CLI config value via `-c key=value`
  - Plain objects flattened recursively to dotted keys (e.g., `{sandbox_workspace_write: {network_access: true}}` → `-c sandbox_workspace_write.network_access=true`)
  - Arrays serialized to JSON strings
  - Enables future Codex features without provider updates
- **Per-call parameter overrides:**
  - `providerOptions['codex-cli']` support with `CodexCliProviderOptions` interface
  - Override `reasoningEffort`, `reasoningSummary`, `reasoningSummaryFormat` per request
  - `textVerbosity` (AI SDK naming convention) maps to internal `modelVerbosity`
  - Per-call `configOverrides` merge with constructor settings (per-call values take precedence)
  - Settings precedence: `providerOptions` > constructor settings > Codex CLI defaults
- **Type exports:**
  - `ReasoningEffort`, `ReasoningSummary`, `ReasoningSummaryFormat`, `ModelVerbosity`
  - `CodexCliProviderOptions` for per-call override typing
- **Documentation:**
  - README section: "Model Parameters & Advanced Options (v0.4.0+)"
  - README section: "Per-call overrides via providerOptions (v0.4.0+)"
  - `docs/ai-sdk-v5/configuration.md`: Comprehensive parameter descriptions with CLI flag mappings
  - `docs/ai-sdk-v5/limitations.md`: Model parameter validation quirks documented
- **Examples:**
  - `examples/advanced-settings.mjs`: Demonstrates constructor-level parameters and advanced features
  - `examples/provider-options.mjs`: Demonstrates per-call override patterns

### Changed

- Extended `CodexCliSettings` interface with 8 new optional properties
- `buildArgs()` method updated to accept merged settings parameter
- `doGenerate()` and `doStream()` now parse provider options and merge with constructor settings
- Validation schema extended to validate new parameters and reject invalid `reasoningSummary` values

### Fixed

- Incorrect `reasoningSummary` type that included invalid 'concise' and 'none' values
- Misleading documentation suggesting 'concise' and 'none' work (they don't)
- False limitation warning about reasoning + webSearch combination (was caused by invalid parameter values)

### Technical Details

- Zero breaking changes - all new parameters are optional
- Full backward compatibility with v0.3.0
- 28 tests passing (17 language model tests including 4 new provider options tests)
- Follows AI SDK v5 standard pattern for provider options (consistent with @ai-sdk/openai)
- Zod schema validation with `.strict()` mode to catch invalid properties

## [0.3.0] - 2025-10-03

### Added

- **Comprehensive tool streaming support** - Real-time monitoring of Codex CLI's autonomous tool execution
  - Tool invocation events (`tool-input-start`, `tool-input-delta`, `tool-input-end`)
  - Tool call events with `providerExecuted: true` (Codex executes tools autonomously)
  - Tool result events with complete output payloads
  - Support for all Codex tool types: `exec`, `patch`, `web_search`, `mcp_tool_call`
- Turn-level usage tracking via `turn.completed` events (requires Codex CLI >= 0.44.0)
- New examples:
  - `streaming-tool-calls.mjs` - Basic tool streaming demonstration
  - `streaming-multiple-tools.mjs` - Complex multi-tool workflows with result tracking
- Comprehensive tool streaming documentation in `examples/README.md`

### Fixed

- **Empty schema handling** - No longer adds `additionalProperties: false` to empty schemas (e.g., from `z.any()`)
- **Text event sequence** - Proper emission of `text-start` before `text-delta` events
- **Stream timing race condition** - Use `setImmediate` to ensure all buffered stdout events process before stream finishes

### Changed

- Updated `@openai/codex` optional dependency from `*` to `^0.44.0` for usage tracking support
- Test fixtures updated to match actual Codex CLI event format (`thread.started` vs `session.created`)

### Limitations

- **No real-time output streaming yet** - Tool outputs delivered in final `tool-result` event via `aggregatedOutput` field, not as incremental deltas. Requires Codex CLI to add output-delta events to experimental JSON format.

## [0.2.0] - 2025-09-30

### Breaking Changes

- **Switched to `--experimental-json` exclusively** (removed deprecated `--json` flag)
- **Native `--output-schema` support for all JSON generation** (removed prompt engineering)
  - When using `generateObject`, the provider now writes the JSON schema to a temp file and passes it via `--output-schema` flag
  - The Codex CLI sends the schema to OpenAI's Responses API with `strict: true`, enforcing JSON at the model level
  - No more manual JSON instructions injected into prompts
- **Removed `extract-json.ts` module** - JSON output is now API-guaranteed to be valid
- **Simplified `mapMessagesToPrompt`** - removed `mode` and `jsonSchema` parameters
- **New event format from experimental JSON output** - event structure changed from old `--json` format

### Added

- Native JSON Schema enforcement via Codex CLI `--output-schema` flag
- Better usage tracking from `turn.completed` events (experimental JSON format)
- Support for `session.created`, `turn.completed`, and `item.completed` event types
- Automatic cleanup of temp schema files after request completion
- New example: `generate-object-native-schema.mjs` demonstrating native schema capabilities
- New example: `experimental-json-events.mjs` showcasing new event format
- New example: `migration-guide-example.mjs` with before/after comparison
- Migration guide: `docs/ai-sdk-v5/migration-0.2.md`

### Improved

- **Token efficiency**: Eliminates 100-200 tokens per JSON request (no prompt engineering overhead)
- **Reliability**: API-level schema enforcement with strict mode > prompt engineering
- **Simpler codebase**: Removed brittle JSON extraction logic and legacy code paths
- **Better event parsing**: Structured experimental JSON format with proper usage tracking

### Removed

- Prompt engineering for JSON mode (previously injected verbose JSON instructions)
- Legacy `--json` flag support (replaced by `--experimental-json`)
- `extract-json.ts` module (no longer needed with native schema)
- `PromptMode` type from `message-mapper.ts`
- Backward compatibility with old event format

## [0.1.0] - 2025-08-19

### Added

- Initial release with AI SDK v5 support
- Support for `generateText`, `streamText`, and `generateObject`
- ChatGPT OAuth authentication via `codex login`
- Configurable approval and sandbox modes
- Examples for basic usage, streaming, and object generation


## Links discovered
- [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

--- LIMITATIONS.md ---
# Known Limitations

## Native JSON Schema Support (v0.2.0+)

### Optional Fields Not Supported

**OpenAI's strict mode** (used by `--output-schema`) **does not support optional fields**. All properties in the schema must be in the `required` array.

**Impact:**

- Zod schemas with `.optional()` fields will cause OpenAI API errors
- The API will return 400 Bad Request with message: "required is required to be supplied and to be an array including every key in properties"

**Workaround:**

- Make all fields required in your Zod schema
- Use descriptions to indicate which fields might be empty/null
- Handle optional logic in your application code after receiving the response

**Example that will NOT work:**

```typescript
const schema = z.object({
  name: z.string(),
  age: z.number(),
  email: z.string().optional(), // ❌ Will cause API error
});
```

**Example that WILL work:**

```typescript
const schema = z.object({
  name: z.string(),
  age: z.number(),
  email: z.string(), // ✅ All fields required
});
```

### Schema Sanitization

The provider automatically sanitizes JSON schemas to remove fields not supported by OpenAI's strict mode:

**Removed fields:**

- `$schema` - JSON Schema metadata
- `$id`, `$ref`, `$defs`, `definitions` - Schema references
- `title`, `examples` - Documentation fields (at schema level, property names are preserved)
- `default` - Default values
- `format` - String format validators (e.g., `email`, `uuid`, `url`)
- `pattern` - Regex patterns

**Supported:**

- `minimum`, `maximum` - Numeric constraints
- `minLength`, `maxLength` - String length constraints
- `minItems`, `maxItems` - Array length constraints
- `enum` - Enumerated values
- `type`, `properties`, `required`, `items` - Core schema fields
- `description` - Field descriptions

**Important:** Property names like "title", "format", etc. are preserved - only schema metadata fields are removed.

### No Format/Pattern Validation

Since `format` and `pattern` fields are removed during sanitization:

- Email format (`.email()`) not enforced by API
- URL format (`.url()`) not enforced by API
- UUID format (`.uuid()`) not enforced by API
- Regex patterns (`.regex()`) not enforced by API

**Workaround:** Use descriptions to guide the model, and validate in your application code:

```typescript
const schema = z.object({
  email: z.string().describe('Valid email address'),
  website: z.string().describe('Full URL starting with https://'),
  id: z.string().describe('UUID v4 format'),
});
```

## Other Limitations

### Usage Tracking

Currently returns `{ inputTokens: 0, outputTokens: 0, totalTokens: 0 }` for all requests. This is a Codex CLI limitation where `turn.completed` events don't consistently populate usage statistics.

### Streaming

**Status:** Not currently supported with `--experimental-json` format (expected in future Codex CLI releases)

The `--experimental-json` output format (introduced in Codex CLI on Sept 25, 2025) currently only emits `item.completed` events with full text content. Incremental streaming via `item.updated` or delta events is **not yet implemented** by OpenAI.

**What this means:**

- `streamText()` works functionally but delivers the entire response in a single chunk after generation completes
- No incremental text deltas - you wait for the full response, then receive it all at once
- The AI SDK's streaming interface is supported, but actual incremental streaming is not available

**Future support:**
The Codex CLI commit message (344d4a1d) explicitly states: "or other item types like `item.output_delta` when we need streaming" and notes "more event types and item types to come."

When OpenAI adds streaming support to the experimental JSON format, this provider will be updated to handle those events and enable true incremental streaming.

### Color Output

When using `color: 'never'` mode (recommended for parsing), Codex CLI still includes ANSI control sequences in some log lines. The provider filters these out, but it's not 100% reliable.


--- README.md ---
# AI SDK Provider for Codex CLI

[![npm version](https://img.shields.io/npm/v/ai-sdk-provider-codex-cli.svg)](https://www.npmjs.com/package/ai-sdk-provider-codex-cli)
[![npm downloads](https://img.shields.io/npm/dm/ai-sdk-provider-codex-cli.svg)](https://www.npmjs.com/package/ai-sdk-provider-codex-cli)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
![Node >= 18](https://img.shields.io/badge/node-%3E%3D18-43853d?logo=node.js&logoColor=white)
![AI SDK v5](https://img.shields.io/badge/AI%20SDK-v5-000?logo=vercel&logoColor=white)
![Modules: ESM + CJS](https://img.shields.io/badge/modules-ESM%20%2B%20CJS-3178c6)
![TypeScript](https://img.shields.io/badge/TypeScript-blue)
[![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/ben-vargas/ai-sdk-provider-codex-cli/issues)
[![Latest Release](https://img.shields.io/github/v/release/ben-vargas/ai-sdk-provider-codex-cli?display_name=tag)](https://github.com/ben-vargas/ai-sdk-provider-codex-cli/releases/latest)

A community provider for Vercel AI SDK v5 that uses OpenAI’s Codex CLI (non‑interactive `codex exec`) to talk to GPT‑5.1 class models (`gpt-5.1`, the Codex-specific `gpt-5.1-codex`, the flagship `gpt-5.1-codex-max`, and the lightweight `gpt-5.1-codex-mini` slugs) with your ChatGPT Plus/Pro subscription. The provider spawns the Codex CLI process, parses its JSONL output, and adapts it to the AI SDK LanguageModelV2 interface. Legacy GPT-5 / GPT-5-codex slugs remain compatible for existing workflows.

- Works with `generateText`, `streamText`, and `generateObject` (native JSON Schema support via `--output-schema`)
- Uses ChatGPT OAuth from `codex login` (tokens in `~/.codex/auth.json`) or `OPENAI_API_KEY`
- Node-only (spawns a local process); supports CI and local dev
- **v0.5.0**: Adds comprehensive logging system with verbose mode and custom logger support
- **v0.3.0**: Adds comprehensive tool streaming support for monitoring autonomous tool execution
- **v0.2.0 Breaking Changes**: Switched to `--experimental-json` and native schema enforcement (see [CHANGELOG](CHANGELOG.md))

## Installation

1. Install and authenticate Codex CLI

```bash
npm i -g @openai/codex
codex login   # or set OPENAI_API_KEY
```

> **⚠️ Version Requirement**: Requires Codex CLI **>= 0.42.0** for `--experimental-json` and `--output-schema` support. **>= 0.60.0 recommended** for `gpt-5.1-codex-max` and `xhigh` reasoning effort. If you supply your own Codex CLI (global install or custom `codexPath`/`allowNpx`), check it with `codex --version` and upgrade if needed. The optional dependency `@openai/codex` in this package pulls a compatible version automatically.
>
> ```bash
> npm i -g @openai/codex@latest
> ```

2. Install provider and AI SDK

```bash
npm i ai ai-sdk-provider-codex-cli
```

## Quick Start

Text generation

```js
import { generateText } from 'ai';
import { codexCli } from 'ai-sdk-provider-codex-cli';

const model = codexCli('gpt-5.1-codex', {
  allowNpx: true,
  skipGitRepoCheck: true,
  approvalMode: 'on-failure',
  sandboxMode: 'workspace-write',
});

const { text } = await generateText({
  model,
  prompt: 'Reply with a single word: hello.',
});
console.log(text);
```

Streaming

```js
import { streamText } from 'ai';
import { codexCli } from 'ai-sdk-provider-codex-cli';

// The provider works with both `gpt-5.1` and `gpt-5.1-codex`; use the latter for
// the Codex CLI specific slug. Legacy `gpt-5` slugs still work if you need them.
const { textStream } = await streamText({
  model: codexCli('gpt-5.1-codex', { allowNpx: true, skipGitRepoCheck: true }),
  prompt: 'Write two short lines of encouragement.',
});
for await (const chunk of textStream) process.stdout.write(chunk);
```

Object generation (Zod)

```js
import { generateObject } from 'ai';
import { z } from 'zod';
import { codexCli } from 'ai-sdk-provider-codex-cli';

const schema = z.object({ name: z.string(), age: z.number().int() });
const { object } = await generateObject({
  model: codexCli('gpt-5.1-codex', { allowNpx: true, skipGitRepoCheck: true }),
  schema,
  prompt: 'Generate a small user profile.',
});
console.log(object);
```

## Features

- AI SDK v5 compatible (LanguageModelV2)
- Streaming and non‑streaming
- **Configurable logging** (v0.5.0+) - Verbose mode, custom loggers, or silent operation
- **Tool streaming support** (v0.3.0+) - Monitor autonomous tool execution in real-time
- **Native JSON Schema support** via `--output-schema` (API-enforced with `strict: true`)
- JSON object generation with Zod schemas (100-200 fewer tokens per request vs prompt engineering)
- Safe defaults for non‑interactive automation (`on-failure`, `workspace-write`, `--skip-git-repo-check`)
- Fallback to `npx @openai/codex` when not on PATH (`allowNpx`)
- Usage tracking from experimental JSON event format

### Tool Streaming (v0.3.0+)

The provider supports comprehensive tool streaming, enabling real-time monitoring of Codex CLI's autonomous tool execution:

```js
import { streamText } from 'ai';
import { codexCli } from 'ai-sdk-provider-codex-cli';

const result = await streamText({
  model: codexCli('gpt-5.1-codex', { allowNpx: true, skipGitRepoCheck: true }),
  prompt: 'List files and count lines in the largest one',
});

for await (const part of result.fullStream) {
  if (part.type === 'tool-call') {
    console.log('🔧 Tool:', part.toolName);
  }
  if (part.type === 'tool-result') {
    console.log('✅ Result:', part.result);
  }
}
```

**What you get:**

- Tool invocation events when Codex starts executing tools (exec, patch, web_search, mcp_tool_call)
- Tool input tracking with full parameter visibility
- Tool result events with complete output payloads
- `providerExecuted: true` on all tool calls (Codex executes autonomously, app doesn't need to)

**Limitation:** Real-time output streaming (`output-delta` events) not yet available. Tool outputs delivered in final `tool-result` event. See `examples/streaming-tool-calls.mjs` and `examples/streaming-multiple-tools.mjs` for usage patterns.

### Logging Configuration (v0.5.0+)

Control logging verbosity and integrate with your observability stack:

```js
import { codexCli } from 'ai-sdk-provider-codex-cli';

// Default: warn/error only (clean production output)
const model = codexCli('gpt-5.1-codex', {
  allowNpx: true,
  skipGitRepoCheck: true,
});

// Verbose mode: enable debug/info logs for troubleshooting
const verboseModel = codexCli('gpt-5.1-codex', {
  allowNpx: true,
  skipGitRepoCheck: true,
  verbose: true, // Shows all log levels
});

// Custom logger: integrate with Winston, Pino, Datadog, etc.
const customModel = codexCli('gpt-5.1-codex', {
  allowNpx: true,
  skipGitRepoCheck: true,
  verbose: true,
  logger: {
    debug: (msg) => myLogger.debug('Codex:', msg),
    info: (msg) => myLogger.info('Codex:', msg),
    warn: (msg) => myLogger.warn('Codex:', msg),
    error: (msg) => myLogger.error('Codex:', msg),
  },
});

// Silent: disable all logging
const silentModel = codexCli('gpt-5.1-codex', {
  allowNpx: true,
  skipGitRepoCheck: true,
  logger: false, // No logs at all
});
```

**Log Levels:**

- `debug`: Detailed execution traces (verbose mode only)
- `info`: General execution flow (verbose mode only)
- `warn`: Warnings and misconfigurations (always shown)
- `error`: Errors and failures (always shown)

**Default Logger:** Adds level tags `[DEBUG]`, `[INFO]`, `[WARN]`, `[ERROR]` to console output. Use a custom logger or `logger: false` if you need different formatting.

See `examples/logging-*.mjs` for complete examples and [docs/ai-sdk-v5/guide.md](docs/ai-sdk-v5/guide.md) for detailed configuration.

### Text Streaming behavior

**Status:** Incremental streaming not currently supported with `--experimental-json` format (expected in future Codex CLI releases)

The `--experimental-json` output format (introduced Sept 25, 2025) currently only emits `item.completed` events with full text content. Incremental streaming via `item.updated` or delta events is not yet implemented by OpenAI.

**What this means:**

- `streamText()` works functionally but delivers the entire response in a single chunk after generation completes
- No incremental text deltas—you wait for the full response, then receive it all at once
- The AI SDK's streaming interface is supported, but actual incremental streaming is not available

**Future support:** The Codex CLI commit (344d4a1d) introducing experimental JSON explicitly notes: "or other item types like `item.output_delta` when we need streaming" and states "more event types and item types to come."

When OpenAI adds streaming support, this provider will be updated to handle those events and enable true incremental streaming.

## Documentation

- Getting started, configuration, and troubleshooting live in `docs/`:
  - [docs/ai-sdk-v5/guide.md](docs/ai-sdk-v5/guide.md) – full usage guide and examples
  - [docs/ai-sdk-v5/configuration.md](docs/ai-sdk-v5/configuration.md) – all settings and how they map to CLI flags
  - [docs/ai-sdk-v5/troubleshooting.md](docs/ai-sdk-v5/troubleshooting.md) – common issues and fixes
  - [docs/ai-sdk-v5/limitations.md](docs/ai-sdk-v5/limitations.md) – known constraints and behavior differences
- See [examples/](examples/) for runnable scripts covering core usage, streaming, permissions/sandboxing, and object generation.

## Authentication

- Preferred: ChatGPT OAuth via `codex login` (stores tokens at `~/.codex/auth.json`)
- Alternative: export `OPENAI_API_KEY` in the provider’s `env` settings (forwarded to the spawned process)

## Configuration (high level)

- `allowNpx`: If true, falls back to `npx -y @openai/codex` when Codex is not on PATH
- `cwd`: Working directory for Codex
- `addDirs`: Extra directories Codex may read/write (repeats `--add-dir`)
- Autonomy/sandbox:
  - `fullAuto` (equivalent to `--full-auto`)
  - `dangerouslyBypassApprovalsAndSandbox` (bypass approvals and sandbox; dangerous)
  - Otherwise the provider writes `-c approval_policy=...` and `-c sandbox_mode=...` for you; defaults to `on-failure` and `workspace-write`
- `skipGitRepoCheck`: enable by default for CI/non‑repo contexts
- `color`: `always` | `never` | `auto`
- `outputLastMessageFile`: by default the provider sets a temp path and reads it to capture final text reliably
- Logging (v0.5.0+):
  - `verbose`: Enable debug/info logs (default: `false` for clean output)
  - `logger`: Custom logger object or `false` to disable all logging

See [docs/ai-sdk-v5/configuration.md](docs/ai-sdk-v5/configuration.md) for the full list and examples.

## Model Parameters & Advanced Options (v0.4.0+)

Control reasoning effort, verbosity, and advanced Codex features at model creation time:

```ts
import { codexCli } from 'ai-sdk-provider-codex-cli';

const model = codexCli('gpt-5.1-codex', {
  allowNpx: true,
  skipGitRepoCheck: true,
  addDirs: ['../shared'],

  // Reasoning & verbosity
  reasoningEffort: 'medium', // minimal | low | medium | high | xhigh (xhigh only on gpt-5.1-codex-max)
  reasoningSummary: 'auto', // auto | detailed (Note: 'concise' and 'none' are rejected by API)
  reasoningSummaryFormat: 'none', // none | experimental
  modelVerbosity: 'high', // low | medium | high

  // Advanced features
  includePlanTool: true, // adds --include-plan-tool
  profile: 'production', // adds --profile production
  oss: false, // adds --oss when true
  webSearch: true, // maps to -c tools.web_search=true

  // MCP servers (stdio + HTTP/RMCP)
  rmcpClient: true, // enables HTTP-based MCP clients (features.rmcp_client=true)
  mcpServers: {
    local: {
      transport: 'stdio',
      command: 'node',
      args: ['tools/mcp.js'],
      env: { API_KEY: process.env.MCP_API_KEY ?? '' },
    },
    docs: {
      transport: 'http',
      url: 'https://mcp.my-org.com',
      bearerTokenEnvVar: 'MCP_BEARER',
      httpHeaders: { 'x-tenant': 'acme' },
    },
  },

  // Generic overrides (maps to -c key=value)
  configOverrides: {
    experimental_resume: '/tmp/session.jsonl',
    sandbox_workspace_write: { network_access: true },
  },
});
```

Nested override objects are flattened to dotted keys (e.g., the example above emits
`-c sandbox_workspace_write.network_access=true`). Arrays are serialized to JSON strings.
MCP server env/header objects flatten the same way (e.g., `mcp_servers.docs.http_headers.x-tenant=acme`).

### Per-call overrides via `providerOptions` (v0.4.0+)

Override these parameters for individual AI SDK calls using the `providerOptions` map. Per-call
values take precedence over constructor defaults while leaving other settings intact.

```ts
import { generateText } from 'ai';
import { codexCli } from 'ai-sdk-provider-codex-cli';

const model = codexCli('gpt-5.1-codex', {
  allowNpx: true,
  reasoningEffort: 'medium',
  modelVerbosity: 'medium',
});

const response = await generateText({
  model,
  prompt: 'Summarize the latest release notes.',
  providerOptions: {
    'codex-cli': {
      reasoningEffort: 'high',
      reasoningSummary: 'detailed',
      textVerbosity: 'high', // AI SDK naming; maps to model_verbosity
      rmcpClient: true,
      mcpServers: {
        scratch: {
          transport: 'stdio',
          command: 'pnpm',
          args: ['mcp', 'serve'],
        },
      },
      configOverrides: {
        experimental_resume: '/tmp/resume.jsonl',
      },
    },
  },
});
```

**Precedence:** `providerOptions['codex-cli']` > constructor `CodexCliSettings` > Codex CLI defaults.

## Zod Compatibility

- Peer supports `zod@^3 || ^4`
- Validation logic normalizes v3/v4 error shapes

## Limitations

- Node ≥ 18, local process only (no Edge)
- Codex `--experimental-json` mode emits events rather than streaming deltas; streaming typically yields a final chunk. The CLI provides the final assistant text in the `item.completed` event, which this provider reads and emits at the end.
- Some AI SDK parameters are unsupported by Codex CLI (e.g., temperature/topP/penalties); the provider surfaces warnings and ignores them

### JSON Schema Limitations (v0.2.0+)

**⚠️ Important:** OpenAI strict mode has limitations:

- **Optional fields NOT supported**: All fields must be required (no `.optional()`)
- **Format validators stripped**: `.email()`, `.url()`, `.uuid()` are removed (use descriptions instead)
- **Pattern validators stripped**: `.regex()` is removed (use descriptions instead)

See [LIMITATIONS.md](LIMITATIONS.md) for comprehensive details and migration guidance.

## Disclaimer

This is a community provider and not an official OpenAI or Vercel product. You are responsible for complying with all applicable terms and ensuring safe usage.

## License

MIT


## Links discovered
- [![npm version](https://img.shields.io/npm/v/ai-sdk-provider-codex-cli.svg)
- [![npm downloads](https://img.shields.io/npm/dm/ai-sdk-provider-codex-cli.svg)
- [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)
- [Node >= 18](https://img.shields.io/badge/node-%3E%3D18-43853d?logo=node.js&logoColor=white)
- [AI SDK v5](https://img.shields.io/badge/AI%20SDK-v5-000?logo=vercel&logoColor=white)
- [Modules: ESM + CJS](https://img.shields.io/badge/modules-ESM%20%2B%20CJS-3178c6)
- [TypeScript](https://img.shields.io/badge/TypeScript-blue)
- [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)
- [![Latest Release](https://img.shields.io/github/v/release/ben-vargas/ai-sdk-provider-codex-cli?display_name=tag)
- [CHANGELOG](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main//CHANGELOG.md)
- [docs/ai-sdk-v5/guide.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main//docs/ai-sdk-v5/guide.md)
- [docs/ai-sdk-v5/configuration.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main//docs/ai-sdk-v5/configuration.md)
- [docs/ai-sdk-v5/troubleshooting.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main//docs/ai-sdk-v5/troubleshooting.md)
- [docs/ai-sdk-v5/limitations.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main//docs/ai-sdk-v5/limitations.md)
- [examples/](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main//examples/)
- [LIMITATIONS.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main//LIMITATIONS.md)

--- eslint.config.js ---
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import globals from 'globals';

export default tseslint.config(
  // Base JS and TS recommended
  eslint.configs.recommended,
  ...tseslint.configs.recommended,

  // Global ignores
  {
    ignores: ['dist/**', 'node_modules/**', 'coverage/**', '.nyc_output/**', 'eslint.config.js'],
  },

  // (parserOptions added per-file block to avoid parsing non-TS files with TS project)

  // Source files - stricter
  {
    files: ['src/**/*.ts'],
    languageOptions: {
      parserOptions: {
        project: './tsconfig.eslint.json',
        tsconfigRootDir: import.meta.dirname,
      },
    },
    rules: {
      '@typescript-eslint/no-explicit-any': 'warn',
      '@typescript-eslint/no-floating-promises': 'error',
      '@typescript-eslint/await-thenable': 'error',
      '@typescript-eslint/no-misused-promises': 'error',
      '@typescript-eslint/no-unused-vars': [
        'error',
        { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
      ],
      'no-console': ['warn', { allow: ['warn', 'error'] }],
      'prefer-const': 'warn',
      'no-empty': 'off',
    },
  },

  // Tests - relaxed
  {
    files: ['**/*.test.ts', '**/*.spec.ts'],
    languageOptions: {
      parserOptions: {
        project: './tsconfig.eslint.json',
        tsconfigRootDir: import.meta.dirname,
      },
      globals: {
        ...globals.node,
        describe: 'readonly',
        it: 'readonly',
        expect: 'readonly',
        beforeEach: 'readonly',
        afterEach: 'readonly',
        beforeAll: 'readonly',
        afterAll: 'readonly',
        vi: 'readonly',
      },
    },
    rules: {
      '@typescript-eslint/no-explicit-any': 'off',
      '@typescript-eslint/no-unsafe-assignment': 'off',
      '@typescript-eslint/no-unsafe-argument': 'off',
      '@typescript-eslint/no-unsafe-member-access': 'off',
      '@typescript-eslint/no-unsafe-call': 'off',
      '@typescript-eslint/no-unsafe-return': 'off',
      '@typescript-eslint/require-await': 'off',
      '@typescript-eslint/unbound-method': 'off',
      '@typescript-eslint/no-unused-vars': [
        'error',
        { argsIgnorePattern: '^_', varsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_' },
      ],
      'no-console': 'off',
    },
  },

  // Examples - relaxed
  {
    files: ['examples/**/*.ts', 'examples/**/*.mjs'],
    languageOptions: {
      globals: { ...globals.node },
    },
    rules: {
      'no-console': 'off',
      '@typescript-eslint/no-explicit-any': 'warn',
      '@typescript-eslint/await-thenable': 'off',
      '@typescript-eslint/no-floating-promises': 'off',
      '@typescript-eslint/no-misused-promises': 'off',
    },
  },
);


--- tsup.config.ts ---
import { defineConfig } from 'tsup';

export default defineConfig({
  entry: ['src/index.ts'],
  splitting: false,
  sourcemap: false,
  clean: true,
  dts: true,
  format: ['esm', 'cjs'],
  target: 'es2022',
  treeshake: true,
});


--- vitest.config.ts ---
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'node',
    include: ['src/**/__tests__/**/*.test.ts'],
    coverage: {
      provider: 'v8',
      reportsDirectory: './coverage',
      reporter: ['text', 'html'],
      exclude: ['dist/**', 'examples/**', 'vitest.config.ts', 'tsup.config.ts'],
    },
  },
});


--- src/codex-cli-language-model.ts ---
import { spawn } from 'node:child_process';
import { randomUUID } from 'node:crypto';
import { createRequire } from 'node:module';
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import type { ReadableStreamDefaultController } from 'node:stream/web';
import { z } from 'zod';
import type {
  LanguageModelV2,
  LanguageModelV2CallWarning,
  LanguageModelV2FinishReason,
  LanguageModelV2StreamPart,
  LanguageModelV2Usage,
  LanguageModelV2Content,
} from '@ai-sdk/provider';
import { NoSuchModelError } from '@ai-sdk/provider';
import { generateId, parseProviderOptions } from '@ai-sdk/provider-utils';
import { getLogger, createVerboseLogger } from './logger.js';
import type {
  CodexCliProviderOptions,
  CodexCliSettings,
  Logger,
  McpServerConfig,
  McpServerStdio,
  McpServerHttp,
} from './types.js';
import { mcpServersSchema, validateModelId } from './validation.js';
import { mapMessagesToPrompt } from './message-mapper.js';
import { createAPICallError, createAuthenticationError } from './errors.js';

export interface CodexLanguageModelOptions {
  id: string; // model id for Codex (-m)
  settings?: CodexCliSettings;
}

// Experimental JSON event format from --experimental-json
interface ExperimentalJsonEvent {
  type?: string;
  session_id?: string;
  thread_id?: string;
  usage?: {
    input_tokens?: number;
    output_tokens?: number;
    cached_input_tokens?: number;
  };
  item?: {
    id?: string;
    item_type?: string; // Flattened from ConversationItemDetails
    text?: string; // For assistant_message and reasoning items
    [k: string]: unknown;
  };
  message?: string; // For error events
  error?: {
    message?: string;
    [k: string]: unknown;
  };
  [k: string]: unknown;
}

type ExperimentalJsonItem = NonNullable<ExperimentalJsonEvent['item']>;

interface ActiveToolItem {
  toolCallId: string;
  toolName: string;
  inputPayload?: unknown;
  hasEmittedCall: boolean;
}

const codexCliProviderOptionsSchema: z.ZodType<CodexCliProviderOptions> = z
  .object({
    reasoningEffort: z.enum(['minimal', 'low', 'medium', 'high', 'xhigh']).optional(),
    reasoningSummary: z.enum(['auto', 'detailed']).optional(),
    reasoningSummaryFormat: z.enum(['none', 'experimental']).optional(),
    textVerbosity: z.enum(['low', 'medium', 'high']).optional(),
    addDirs: z.array(z.string().min(1)).optional(),
    configOverrides: z
      .record(
        z.string(),
        z.union([
          z.string(),
          z.number(),
          z.boolean(),
          z.object({}).passthrough(),
          z.array(z.any()),
        ]),
      )
      .optional(),
    mcpServers: mcpServersSchema.optional(),
    rmcpClient: z.boolean().optional(),
  })
  .strict();

function resolveCodexPath(
  explicitPath?: string,
  allowNpx?: boolean,
): { cmd: string; args: string[] } {
  if (explicitPath) return { cmd: 'node', args: [explicitPath] };

  try {
    const req = createRequire(import.meta.url);
    const pkgPath = req.resolve('@openai/codex/package.json');
    const root = pkgPath.replace(/package\.json$/, '');
    return { cmd: 'node', args: [root + 'bin/codex.js'] };
  } catch {
    // Fallback to PATH or npx
    if (allowNpx) return { cmd: 'npx', args: ['-y', '@openai/codex'] };
    return { cmd: 'codex', args: [] };
  }
}

export class CodexCliLanguageModel implements LanguageModelV2 {
  readonly specificationVersion = 'v2' as const;
  readonly provider = 'codex-cli';
  readonly defaultObjectGenerationMode = 'json' as const;
  readonly supportsImageUrls = false;
  readonly supportedUrls = {};
  readonly supportsStructuredOutputs = true;

  readonly modelId: string;
  readonly settings: CodexCliSettings;

  private logger: Logger;
  private sessionId?: string;

  constructor(options: CodexLanguageModelOptions) {
    this.modelId = options.id;
    this.settings = options.settings ?? {};
    const baseLogger = getLogger(this.settings.logger);
    this.logger = createVerboseLogger(baseLogger, this.settings.verbose ?? false);
    if (!this.modelId || this.modelId.trim() === '') {
      throw new NoSuchModelError({ modelId: this.modelId, modelType: 'languageModel' });
    }
    const warn = validateModelId(this.modelId);
    if (warn) this.logger.warn(`Codex CLI model: ${warn}`);
  }

  private mergeSettings(providerOptions?: CodexCliProviderOptions): CodexCliSettings {
    if (!providerOptions) return this.settings;

    const mergedConfigOverrides =
      providerOptions.configOverrides || this.settings.configOverrides
        ? {
            ...(this.settings.configOverrides ?? {}),
            ...(providerOptions.configOverrides ?? {}),
          }
        : undefined;

    const mergedAddDirs =
      providerOptions.addDirs || this.settings.addDirs
        ? [...(this.settings.addDirs ?? []), ...(providerOptions.addDirs ?? [])]
        : undefined;

    const mergedMcpServers = this.mergeMcpServers(
      this.settings.mcpServers,
      providerOptions.mcpServers,
    );

    return {
      ...this.settings,
      reasoningEffort: providerOptions.reasoningEffort ?? this.settings.reasoningEffort,
      reasoningSummary: providerOptions.reasoningSummary ?? this.settings.reasoningSummary,
      reasoningSummaryFormat:
        providerOptions.reasoningSummaryFormat ?? this.settings.reasoningSummaryFormat,
      modelVerbosity: providerOptions.textVerbosity ?? this.settings.modelVerbosity,
      configOverrides: mergedConfigOverrides,
      addDirs: mergedAddDirs,
      mcpServers: mergedMcpServers,
      rmcpClient: providerOptions.rmcpClient ?? this.settings.rmcpClient,
    };
  }

  private mergeMcpServers(
    base?: Record<string, McpServerConfig>,
    override?: Record<string, McpServerConfig>,
  ): Record<string, McpServerConfig> | undefined {
    if (!base) return override;
    if (!override) return base;

    const merged: Record<string, McpServerConfig> = { ...base };
    for (const [name, incoming] of Object.entries(override)) {
      const existing = base[name];
      merged[name] = this.mergeSingleMcpServer(existing, incoming);
    }
    return merged;
  }

  private mergeSingleMcpServer(
    existing: McpServerConfig | undefined,
    incoming: McpServerConfig,
  ): McpServerConfig {
    if (!existing || existing.transport !== incoming.transport) {
      return { ...incoming };
    }

    if (incoming.transport === 'stdio') {
      const baseStdio = existing as McpServerStdio;
      const result: McpServerConfig = {
        transport: 'stdio',
        command: incoming.command,
        args: incoming.args ?? baseStdio.args,
        env: this.mergeStringRecord(baseStdio.env, incoming.env),
        cwd: incoming.cwd ?? baseStdio.cwd,
        enabled: incoming.enabled ?? existing.enabled,
        startupTimeoutSec: incoming.startupTimeoutSec ?? existing.startupTimeoutSec,
        toolTimeoutSec: incoming.toolTimeoutSec ?? existing.toolTimeoutSec,
        enabledTools: incoming.enabledTools ?? existing.enabledTools,
        disabledTools: incoming.disabledTools ?? existing.disabledTools,
      } as McpServerConfig;
      return result;
    }

    const baseHttp = existing as McpServerHttp;
    // Treat auth fields as a bundle: if incoming defines either, override both.
    const hasIncomingAuth =
      incoming.bearerToken !== undefined || incoming.bearerTokenEnvVar !== undefined;
    const bearerToken = hasIncomingAuth ? incoming.bearerToken : baseHttp.bearerToken;
    const bearerTokenEnvVar = hasIncomingAuth
      ? incoming.bearerTokenEnvVar
      : baseHttp.bearerTokenEnvVar;

    const result: McpServerConfig = {
      transport: 'http',
      url: incoming.url,
      bearerToken,
      bearerTokenEnvVar,
      httpHeaders: this.mergeStringRecord(baseHttp.httpHeaders, incoming.httpHeaders),
      envHttpHeaders: this.mergeStringRecord(baseHttp.envHttpHeaders, incoming.envHttpHeaders),
      enabled: incoming.enabled ?? existing.enabled,
      startupTimeoutSec: incoming.startupTimeoutSec ?? existing.startupTimeoutSec,
      toolTimeoutSec: incoming.toolTimeoutSec ?? existing.toolTimeoutSec,
      enabledTools: incoming.enabledTools ?? existing.enabledTools,
      disabledTools: incoming.disabledTools ?? existing.disabledTools,
    };

    return result;
  }

  private mergeStringRecord(
    base?: Record<string, string>,
    override?: Record<string, string>,
  ): Record<string, string> | undefined {
    if (override !== undefined) {
      if (Object.keys(override).length === 0) return {};
      return { ...(base ?? {}), ...override };
    }
    if (base) return { ...base };
    return undefined;
  }

  // Codex JSONL items use `type` for the item discriminator, but some
  // earlier fixtures (and defensive parsing) might still surface `item_type`.
  // This helper returns whichever is present.
  private getItemType(item?: ExperimentalJsonItem): string | undefined {
    if (!item) return undefined;
    const data = item as Record<string, unknown>;
    const legacy = typeof data.item_type === 'string' ? (data.item_type as string) : undefined;
    const current = typeof data.type === 'string' ? (data.type as string) : undefined;
    return legacy ?? current;
  }

  private buildArgs(
    promptText: string,
    responseFormat?: { type: 'json'; schema: unknown },
    settings: CodexCliSettings = this.settings,
  ): {
    cmd: string;
    args: string[];
    env: NodeJS.ProcessEnv;
    cwd?: string;
    lastMessagePath?: string;
    lastMessageIsTemp?: boolean;
    schemaPath?: string;
  } {
    const base = resolveCodexPath(settings.codexPath, settings.allowNpx);
    const args: string[] = [...base.args, 'exec', '--experimental-json'];

    // Approval/sandbox (exec subcommand does not accept -a/-s directly; use -c overrides)
    if (settings.fullAuto) {
      args.push('--full-auto');
    } else if (settings.dangerouslyBypassApprovalsAndSandbox) {
      args.push('--dangerously-bypass-approvals-and-sandbox');
    } else {
      const approval = settings.approvalMode ?? 'on-failure';
      args.push('-c', `approval_policy=${approval}`);
      const sandbox = settings.sandboxMode ?? 'workspace-write';
      args.push('-c', `sandbox_mode=${sandbox}`);
    }

    if (settings.skipGitRepoCheck !== false) {
      args.push('--skip-git-repo-check');
    }

    // Reasoning & verbosity
    if (settings.reasoningEffort) {
      args.push('-c', `model_reasoning_effort=${settings.reasoningEffort}`);
    }
    if (settings.reasoningSummary) {
      args.push('-c', `model_reasoning_summary=${settings.reasoningSummary}`);
    }
    if (settings.reasoningSummaryFormat) {
      args.push('-c', `model_reasoning_summary_format=${settings.reasoningSummaryFormat}`);
    }
    if (settings.modelVerbosity) {
      args.push('-c', `model_verbosity=${settings.modelVerbosity}`);
    }

    // Advanced Codex features
    if (settings.includePlanTool) {
      args.push('--include-plan-tool');
    }
    if (settings.profile) {
      args.push('--profile', settings.profile);
    }
    if (settings.oss) {
      args.push('--oss');
    }
    if (settings.webSearch) {
      args.push('-c', 'tools.web_search=true');
    }

    // MCP configuration
    this.applyMcpSettings(args, settings);

    // Color handling
    if (settings.color) {
      args.push('--color', settings.color);
    }

    if (this.modelId) {
      args.push('-m', this.modelId);
    }

    if (settings.addDirs?.length) {
      for (const dir of settings.addDirs) {
        if (typeof dir === 'string' && dir.trim().length > 0) {
          args.push('--add-dir', dir);
        }
      }
    }

    // Generic config overrides (-c key=value)
    if (settings.configOverrides) {
      for (const [key, value] of Object.entries(settings.configOverrides)) {
        this.addConfigOverride(args, key, value);
      }
    }

    // Handle JSON schema if provided
    let schemaPath: string | undefined;
    if (responseFormat?.type === 'json' && responseFormat.schema) {
      const schema = typeof responseFormat.schema === 'object' ? responseFormat.schema : {};
      const sanitizedSchema = this.sanitizeJsonSchema(schema) as Record<string, unknown>;

      // Only write schema if it has properties (not empty schema like z.any())
      const hasProperties = Object.keys(sanitizedSchema).length > 0;
      if (hasProperties) {
        const dir = mkdtempSync(join(tmpdir(), 'codex-schema-'));
        schemaPath = join(dir, 'schema.json');

        // OpenAI strict mode requires additionalProperties=false for structured schemas
        const schemaWithAdditional = {
          ...sanitizedSchema,
          additionalProperties: false,
        };

        writeFileSync(schemaPath, JSON.stringify(schemaWithAdditional, null, 2));
        args.push('--output-schema', schemaPath);
      }
    }

    // Prompt as positional arg (avoid stdin for reliability)
    args.push(promptText);

    const env: NodeJS.ProcessEnv = {
      ...process.env,
      ...(settings.env || {}),
      RUST_LOG: process.env.RUST_LOG || 'error',
    };

    // Configure output-last-message
    let lastMessagePath: string | undefined = settings.outputLastMessageFile;
    let lastMessageIsTemp = false;
    if (!lastMessagePath) {
      // create a temp folder for this run
      const dir = mkdtempSync(join(tmpdir(), 'codex-cli-'));
      lastMessagePath = join(dir, 'last-message.txt');
      lastMessageIsTemp = true;
    }
    args.push('--output-last-message', lastMessagePath);

    return {
      cmd: base.cmd,
      args,
      env,
      cwd: settings.cwd,
      lastMessagePath,
      lastMessageIsTemp,
      schemaPath,
    };
  }

  private applyMcpSettings(args: string[], settings: CodexCliSettings): void {
    if (settings.rmcpClient) {
      this.addConfigOverride(args, 'features.rmcp_client', true);
    }

    if (!settings.mcpServers) return;

    for (const [rawName, server] of Object.entries(settings.mcpServers)) {
      const name = rawName.trim();
      if (!name) continue;
      const prefix = `mcp_servers.${name}`;

      if (server.enabled !== undefined) {
        this.addConfigOverride(args, `${prefix}.enabled`, server.enabled);
      }
      if (server.startupTimeoutSec !== undefined) {
        this.addConfigOverride(args, `${prefix}.startup_timeout_sec`, server.startupTimeoutSec);
      }
      if (server.toolTimeoutSec !== undefined) {
        this.addConfigOverride(args, `${prefix}.tool_timeout_sec`, server.toolTimeoutSec);
      }
      if (server.enabledTools !== undefined) {
        this.addConfigOverride(args, `${prefix}.enabled_tools`, server.enabledTools);
      }
      if (server.disabledTools !== undefined) {
        this.addConfigOverride(args, `${prefix}.disabled_tools`, server.disabledTools);
      }

      if (server.transport === 'stdio') {
        this.addConfigOverride(args, `${prefix}.command`, server.command);
        if (server.args !== undefined) this.addConfigOverride(args, `${prefix}.args`, server.args);
        if (server.env !== undefined) this.addConfigOverride(args, `${prefix}.env`, server.env);
        if (server.cwd) this.addConfigOverride(args, `${prefix}.cwd`, server.cwd);
      } else {
        this.addConfigOverride(args, `${prefix}.url`, server.url);
        if (server.bearerToken !== undefined)
          this.addConfigOverride(args, `${prefix}.bearer_token`, server.bearerToken);
        if (server.bearerTokenEnvVar)
          this.addConfigOverride(args, `${prefix}.bearer_token_env_var`, server.bearerTokenEnvVar);
        if (server.httpHeaders !== undefined)
          this.addConfigOverride(args, `${prefix}.http_headers`, server.httpHeaders);
        if (server.envHttpHeaders !== undefined)
          this.addConfigOverride(args, `${prefix}.env_http_headers`, server.envHttpHeaders);
      }
    }
  }

  private addConfigOverride(
    args: string[],
    key: string,
    value: string | number | boolean | object,
  ): void {
    if (this.isPlainObject(value)) {
      const entries = Object.entries(value);
      if (entries.length === 0) {
        args.push('-c', `${key}={}`);
        return;
      }
      for (const [childKey, childValue] of entries) {
        this.addConfigOverride(
          args,
          `${key}.${childKey}`,
          childValue as string | number | boolean | object,
        );
      }
      return;
    }
    const serialized = this.serializeConfigValue(value);
    args.push('-c', `${key}=${serialized}`);
  }

  /**
   * Serialize a config override value into a CLI-safe string.
   */
  private serializeConfigValue(value: string | number | boolean | object): string {
    if (typeof value === 'string') return value;
    if (typeof value === 'number' || typeof value === 'boolean') return String(value);
    if (Array.isArray(value)) {
      try {
        return JSON.stringify(value);
      } catch {
        return String(value);
      }
    }
    if (value && typeof value === 'object') {
      // Remaining plain objects are flattened earlier; fallback to JSON.
      try {
        return JSON.stringify(value);
      } catch {
        return String(value);
      }
    }
    return String(value);
  }

  private isPlainObject(value: unknown): value is Record<string, unknown> {
    return (
      typeof value === 'object' &&
      value !== null &&
      !Array.isArray(value) &&
      Object.prototype.toString.call(value) === '[object Object]'
    );
  }

  private sanitizeJsonSchema(value: unknown): unknown {
    // Remove fields that OpenAI strict mode doesn't support
    // Based on codex-rs/core/src/openai_tools.rs sanitize_json_schema
    if (typeof value !== 'object' || value === null) {
      return value;
    }

    if (Array.isArray(value)) {
      return value.map((item) => this.sanitizeJsonSchema(item));
    }

    const obj = value as Record<string, unknown>;
    const result: Record<string, unknown> = {};

    for (const [key, val] of Object.entries(obj)) {
      // Special handling for 'properties' - preserve all property names, sanitize their schemas
      if (key === 'properties' && typeof val === 'object' && val !== null && !Array.isArray(val)) {
        const props = val as Record<string, unknown>;
        const sanitizedProps: Record<string, unknown> = {};
        for (const [propName, propSchema] of Object.entries(props)) {
          // Keep property name, sanitize its schema
          sanitizedProps[propName] = this.sanitizeJsonSchema(propSchema);
        }
        result[key] = sanitizedProps;
        continue;
      }

      // Remove unsupported metadata fields
      if (
        key === '$schema' ||
        key === '$id' ||
        key === '$ref' ||
        key === '$defs' ||
        key === 'definitions' ||
        key === 'title' ||
        key === 'examples' ||
        key === 'default' ||
        key === 'format' || // OpenAI strict mode doesn't support format
        key === 'pattern' // OpenAI strict mode doesn't support pattern
      ) {
        continue;
      }

      // Recursively sanitize nested objects and arrays
      result[key] = this.sanitizeJsonSchema(val);
    }

    return result;
  }

  private mapWarnings(
    options: Parameters<LanguageModelV2['doGenerate']>[0],
  ): LanguageModelV2CallWarning[] {
    const unsupported: LanguageModelV2CallWarning[] = [];
    const add = (setting: unknown, name: string) => {
      if (setting !== undefined)
        unsupported.push({
          type: 'unsupported-setting',
          setting: name,
          details: `Codex CLI does not support ${name}; it will be ignored.`,
        } as LanguageModelV2CallWarning);
    };
    add(options.temperature, 'temperature');
    add(options.topP, 'topP');
    add(options.topK, 'topK');
    add(options.presencePenalty, 'presencePenalty');
    add(options.frequencyPenalty, 'frequencyPenalty');
    add(options.stopSequences?.length ? options.stopSequences : undefined, 'stopSequences');
    add((options as { seed?: unknown }).seed, 'seed');
    return unsupported;
  }

  private parseExperimentalJsonEvent(line: string): ExperimentalJsonEvent | undefined {
    try {
      return JSON.parse(line) as ExperimentalJsonEvent;
    } catch {
      return undefined;
    }
  }

  private extractUsage(evt: ExperimentalJsonEvent): LanguageModelV2Usage | undefined {
    const reported = evt.usage;
    if (!reported) return undefined;
    const inputTokens = reported.input_tokens ?? 0;
    const outputTokens = reported.output_tokens ?? 0;
    const cachedInputTokens = reported.cached_input_tokens ?? 0;
    return {
      inputTokens,
      outputTokens,
      // totalTokens should not double-count cached tokens; track cached separately
      totalTokens: inputTokens + outputTokens,
      cachedInputTokens,
    };
  }

  private getToolName(item?: ExperimentalJsonItem): string | undefined {
    if (!item) return undefined;
    const itemType = this.getItemType(item);
    switch (itemType) {
      case 'command_execution':
        return 'exec';
      case 'file_change':
        return 'patch';
      case 'mcp_tool_call': {
        const tool = (item as Record<string, unknown>).tool;
        if (typeof tool === 'string' && tool.length > 0) return tool;
        return 'mcp_tool';
      }
      case 'web_search':
        return 'web_search';
      default:
        return undefined;
    }
  }

  private buildToolInputPayload(item?: ExperimentalJsonItem): unknown {
    if (!item) return undefined;
    const data = item as Record<string, unknown>;
    switch (this.getItemType(item)) {
      case 'command_execution': {
        const payload: Record<string, unknown> = {};
        if (typeof data.command === 'string') payload.command = data.command;
        if (typeof data.status === 'string') payload.status = data.status;
        if (typeof data.cwd === 'string') payload.cwd = data.cwd;
        return Object.keys(payload).length ? payload : undefined;
      }
      case 'file_change': {
        const payload: Record<string, unknown> = {};
        if (Array.isArray(data.changes)) payload.changes = data.changes;
        if (typeof data.status === 'string') payload.status = data.status;
        return Object.keys(payload).length ? payload : undefined;
      }
      case 'mcp_tool_call': {
        const payload: Record<string, unknown> = {};
        if (typeof data.server === 'string') payload.server = data.server;
        if (typeof data.tool === 'string') payload.tool = data.tool;
        if (typeof data.status === 'string') payload.status = data.status;
        // Include arguments so consumers can see what parameters were passed
        if (data.arguments !== undefined) payload.arguments = data.arguments;
        return Object.keys(payload).length ? payload : undefined;
      }
      case 'web_search': {
        const payload: Record<string, unknown> = {};
        if (typeof data.query === 'string') payload.query = data.query;
        return Object.keys(payload).length ? payload : undefined;
      }
      default:
        return undefined;
    }
  }

  private buildToolResultPayload(item?: ExperimentalJsonItem): {
    result: unknown;
    metadata?: Record<string, string>;
  } {
    if (!item) return { result: {} };
    const data = item as Record<string, unknown>;
    const metadata: Record<string, string> = {};
    const itemType = this.getItemType(item);
    if (typeof itemType === 'string') metadata.itemType = itemType;
    if (typeof item.id === 'string') metadata.itemId = item.id;
    if (typeof data.status === 'string') metadata.status = data.status;

    const buildResult = (result: Record<string, unknown>) => ({
      result,
      metadata: Object.keys(metadata).length ? metadata : undefined,
    });

    switch (itemType) {
      case 'command_execution': {
        const result: Record<string, unknown> = {};
        if (typeof data.command === 'string') result.command = data.command;
        if (typeof data.aggregated_output === 'string')
          result.aggregatedOutput = data.aggregated_output;
        if (typeof data.exit_code === 'number') result.exitCode = data.exit_code;
        if (typeof data.status === 'string') result.status = data.status;
        return buildResult(result);
      }
      case 'file_change': {
        const result: Record<string, unknown> = {};
        if (Array.isArray(data.changes)) result.changes = data.changes;
        if (typeof data.status === 'string') result.status = data.status;
        return buildResult(result);
      }
      case 'mcp_tool_call': {
        const result: Record<string, unknown> = {};
        if (typeof data.server === 'string') {
          result.server = data.server;
          metadata.server = data.server;
        }
        if (typeof data.tool === 'string') result.tool = data.tool;
        if (typeof data.status === 'string') result.status = data.status;
        // Include result payload so consumers can see what the tool returned
        if (data.result !== undefined) result.result = data.result;
        // Include error details if present
        if (data.error !== undefined) result.error = data.error;
        return buildResult(result);
      }
      case 'web_search': {
        const result: Record<string, unknown> = {};
        if (typeof data.query === 'string') result.query = data.query;
        if (typeof data.status === 'string') result.status = data.status;
        return buildResult(result);
      }
      default: {
        const result = { ...data };
        return buildResult(result);
      }
    }
  }

  private safeStringify(value: unknown): string {
    if (value === undefined) return '';
    if (typeof value === 'string') return value;
    try {
      return JSON.stringify(value);
    } catch {
      return '';
    }
  }

  private emitToolInvocation(
    controller: ReadableStreamDefaultController<LanguageModelV2StreamPart>,
    toolCallId: string,
    toolName: string,
    inputPayload: unknown,
  ): void {
    const inputString = this.safeStringify(inputPayload);
    controller.enqueue({ type: 'tool-input-start', id: toolCallId, toolName });
    if (inputString) {
      controller.enqueue({ type: 'tool-input-delta', id: toolCallId, delta: inputString });
    }
    controller.enqueue({ type: 'tool-input-end', id: toolCallId });
    controller.enqueue({
      type: 'tool-call',
      toolCallId,
      toolName,
      input: inputString,
      providerExecuted: true,
    });
  }

  private emitToolResult(
    controller: ReadableStreamDefaultController<LanguageModelV2StreamPart>,
    toolCallId: string,
    toolName: string,
    item: ExperimentalJsonItem,
    resultPayload: unknown,
    metadata?: Record<string, string>,
  ): void {
    const providerMetadataEntries: Record<string, string> = {
      ...(metadata ?? {}),
    };
    const itemType = this.getItemType(item);
    if (itemType && providerMetadataEntries.itemType === undefined) {
      providerMetadataEntries.itemType = itemType;
    }
    if (item.id && providerMetadataEntries.itemId === undefined) {
      providerMetadataEntries.itemId = item.id;
    }

    // Determine error status for command executions
    let isError: boolean | undefined;
    if (itemType === 'command_execution') {
      const data = item as Record<string, unknown>;
      const exitCode = typeof data.exit_code === 'number' ? (data.exit_code as number) : undefined;
      const status = typeof data.status === 'string' ? (data.status as string) : undefined;
      if ((exitCode !== undefined && exitCode !== 0) || status === 'failed') {
        isError = true;
      }
    }

    controller.enqueue({
      type: 'tool-result',
      toolCallId,
      toolName,
      result: resultPayload ?? {},
      ...(isError ? { isError: true } : {}),
      ...(Object.keys(providerMetadataEntries).length
        ? { providerMetadata: { 'codex-cli': providerMetadataEntries } }
        : {}),
    });
  }

  private handleSpawnError(err: unknown, promptExcerpt: string) {
    const e =
      err && typeof err === 'object'
        ? (err as {
            message?: unknown;
            code?: unknown;
            exitCode?: unknown;
            stderr?: unknown;
          })
        : undefined;
    const message = String((e?.message ?? err) || 'Failed to run Codex CLI');
    // crude auth detection
    if (/login|auth|unauthorized|not\s+logged/i.test(message)) {
      throw createAuthenticationError(message);
    }
    throw createAPICallError({
      message,
      code: typeof e?.code === 'string' ? e.code : undefined,
      exitCode: typeof e?.exitCode === 'number' ? e.exitCode : undefined,
      stderr: typeof e?.stderr === 'string' ? e.stderr : undefined,
      promptExcerpt,
    });
  }

  async doGenerate(
    options: Parameters<LanguageModelV2['doGenerate']>[0],
  ): Promise<Awaited<ReturnType<LanguageModelV2['doGenerate']>>> {
    this.logger.debug(`[codex-cli] Starting doGenerate request with model: ${this.modelId}`);

    const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
    const promptExcerpt = promptText.slice(0, 200);
    const warnings = [
      ...this.mapWarnings(options),
      ...(mappingWarnings?.map((m) => ({ type: 'other', message: m })) || []),
    ] as LanguageModelV2CallWarning[];

    this.logger.debug(
      `[codex-cli] Converted ${options.prompt.length} messages, response format: ${options.responseFormat?.type ?? 'none'}`,
    );

    const providerOptions = await parseProviderOptions<CodexCliProviderOptions>({
      provider: this.provider,
      providerOptions: options.providerOptions,
      schema: codexCliProviderOptionsSchema,
    });
    const effectiveSettings = this.mergeSettings(providerOptions);

    const responseFormat =
      options.responseFormat?.type === 'json'
        ? { type: 'json' as const, schema: options.responseFormat.schema }
        : undefined;
    const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath } = this.buildArgs(
      promptText,
      responseFormat,
      effectiveSettings,
    );

    this.logger.debug(
      `[codex-cli] Executing Codex CLI: ${cmd} with ${args.length} arguments, cwd: ${cwd ?? 'default'}`,
    );

    let text = '';
    const usage: LanguageModelV2Usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
    const finishReason: LanguageModelV2FinishReason = 'stop';
    const startTime = Date.now();

    const child = spawn(cmd, args, { env, cwd, stdio: ['ignore', 'pipe', 'pipe'] });

    // Abort support
    let onAbort: (() => void) | undefined;
    if (options.abortSignal) {
      if (options.abortSignal.aborted) {
        child.kill('SIGTERM');
        throw options.abortSignal.reason ?? new Error('Request aborted');
      }
      onAbort = () => child.kill('SIGTERM');
      options.abortSignal.addEventListener('abort', onAbort, { once: true });
    }

    try {
      await new Promise<void>((resolve, reject) => {
        let stderr = '';
        let turnFailureMessage: string | undefined;
        child.stderr.on('data', (d) => (stderr += String(d)));
        child.stdout.setEncoding('utf8');
        child.stdout.on('data', (chunk: string) => {
          const lines = chunk.split(/\r?\n/).filter(Boolean);
          for (const line of lines) {
            const event = this.parseExperimentalJsonEvent(line);
            if (!event) continue;

            this.logger.debug(`[codex-cli] Received event type: ${event.type ?? 'unknown'}`);

            if (event.type === 'thread.started' && typeof event.thread_id === 'string') {
              this.sessionId = event.thread_id;
              this.logger.debug(`[codex-cli] Session started: ${this.sessionId}`);
            }
            if (event.type === 'session.created' && typeof event.session_id === 'string') {
              // Backwards compatibility in case older events appear
              this.sessionId = event.session_id;
              this.logger.debug(`[codex-cli] Session created: ${this.sessionId}`);
            }

            if (event.type === 'turn.completed') {
              const usageEvent = this.extractUsage(event);
              if (usageEvent) {
                usage.inputTokens = usageEvent.inputTokens;
                usage.outputTokens = usageEvent.outputTokens;
                usage.totalTokens = usageEvent.totalTokens;
              }
            }

            if (
              event.type === 'item.completed' &&
              this.getItemType(event.item) === 'assistant_message' &&
              typeof event.item?.text === 'string'
            ) {
              text = event.item.text;
            }

            if (event.type === 'turn.failed') {
              const errorText =
                (event.error && typeof event.error.message === 'string' && event.error.message) ||
                (typeof event.message === 'string' ? event.message : undefined);
              turnFailureMessage = errorText ?? turnFailureMessage ?? 'Codex turn failed';
              this.logger.error(`[codex-cli] Turn failed: ${turnFailureMessage}`);
            }

            if (event.type === 'error') {
              const errorText = typeof event.message === 'string' ? event.message : undefined;
              turnFailureMessage = errorText ?? turnFailureMessage ?? 'Codex error';
              this.logger.error(`[codex-cli] Error event: ${turnFailureMessage}`);
            }
          }
        });
        child.on('error', (e) => {
          this.logger.error(`[codex-cli] Spawn error: ${String(e)}`);
          reject(this.handleSpawnError(e, promptExcerpt));
        });
        child.on('close', (code) => {
          const duration = Date.now() - startTime;
          if (code === 0) {
            if (turnFailureMessage) {
              reject(
                createAPICallError({
                  message: turnFailureMessage,
                  stderr,
                  promptExcerpt,
                }),
              );
              return;
            }
            this.logger.info(
              `[codex-cli] Request completed - Session: ${this.sessionId ?? 'N/A'}, Duration: ${duration}ms, Tokens: ${usage.totalTokens}`,
            );
            this.logger.debug(
              `[codex-cli] Token usage - Input: ${usage.inputTokens}, Output: ${usage.outputTokens}, Total: ${usage.totalTokens}`,
            );
            resolve();
          } else {
            this.logger.error(`[codex-cli] Process exited with code ${code} after ${duration}ms`);
            reject(
              createAPICallError({
                message: `Codex CLI exited with code ${code}`,
                exitCode: code ?? undefined,
                stderr,
                promptExcerpt,
              }),
            );
          }
        });
      });
    } finally {
      if (options.abortSignal && onAbort) options.abortSignal.removeEventListener('abort', onAbort);
      // Clean up temp schema file
      if (schemaPath) {
        try {
          const schemaDir = dirname(schemaPath);
          rmSync(schemaDir, { recursive: true, force: true });
        } catch {}
      }
    }

    // Fallback: read last message file if needed
    if (!text && lastMessagePath) {
      try {
        const fileText = readFileSync(lastMessagePath, 'utf8');
        if (fileText && typeof fileText === 'string') {
          text = fileText.trim();
        }
      } catch {}
      // best-effort cleanup for temp paths only
      if (lastMessageIsTemp) {
        try {
          rmSync(lastMessagePath, { force: true });
        } catch {}
      }
    }

    // No JSON extraction needed - native schema guarantees valid JSON

    const content: LanguageModelV2Content[] = [{ type: 'text', text }];
    return {
      content,
      usage,
      finishReason,
      warnings,
      response: { id: generateId(), timestamp: new Date(), modelId: this.modelId },
      request: { body: promptText },
      providerMetadata: {
        'codex-cli': { ...(this.sessionId ? { sessionId: this.sessionId } : {}) },
      },
    };
  }

  async doStream(
    options: Parameters<LanguageModelV2['doStream']>[0],
  ): Promise<Awaited<ReturnType<LanguageModelV2['doStream']>>> {
    this.logger.debug(`[codex-cli] Starting doStream request with model: ${this.modelId}`);

    const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
    const promptExcerpt = promptText.slice(0, 200);
    const warnings = [
      ...this.mapWarnings(options),
      ...(mappingWarnings?.map((m) => ({ type: 'other', message: m })) || []),
    ] as LanguageModelV2CallWarning[];

    this.logger.debug(
      `[codex-cli] Converted ${options.prompt.length} messages for streaming, response format: ${options.responseFormat?.type ?? 'none'}`,
    );

    const providerOptions = await parseProviderOptions<CodexCliProviderOptions>({
      provider: this.provider,
      providerOptions: options.providerOptions,
      schema: codexCliProviderOptionsSchema,
    });
    const effectiveSettings = this.mergeSettings(providerOptions);

    const responseFormat =
      options.responseFormat?.type === 'json'
        ? { type: 'json' as const, schema: options.responseFormat.schema }
        : undefined;
    const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath } = this.buildArgs(
      promptText,
      responseFormat,
      effectiveSettings,
    );

    this.logger.debug(
      `[codex-cli] Executing Codex CLI for streaming: ${cmd} with ${args.length} arguments`,
    );

    const stream = new ReadableStream<LanguageModelV2StreamPart>({
      start: (controller) => {
        const startTime = Date.now();
        const child = spawn(cmd, args, { env, cwd, stdio: ['ignore', 'pipe', 'pipe'] });

        // Emit stream-start
        controller.enqueue({ type: 'stream-start', warnings });

        let stderr = '';
        let accumulatedText = '';
        const activeTools = new Map<string, ActiveToolItem>();
        let responseMetadataSent = false;
        let lastUsage: LanguageModelV2Usage | undefined;
        let turnFailureMessage: string | undefined;

        const sendMetadata = (meta: Record<string, string> = {}) => {
          controller.enqueue({
            type: 'response-metadata',
            id: randomUUID(),
            timestamp: new Date(),
            modelId: this.modelId,
            ...(Object.keys(meta).length ? { providerMetadata: { 'codex-cli': meta } } : {}),
          });
        };

        const handleItemEvent = (event: ExperimentalJsonEvent) => {
          const item = event.item;
          if (!item) return;

          if (
            event.type === 'item.completed' &&
            this.getItemType(item) === 'assistant_message' &&
            typeof item.text === 'string'
          ) {
            accumulatedText = item.text;
            this.logger.debug(
              `[codex-cli] Received assistant message, length: ${item.text.length}`,
            );
            return;
          }

          const toolName = this.getToolName(item);
          if (!toolName) {
            return;
          }

          this.logger.debug(
            `[codex-cli] Tool detected: ${toolName}, item type: ${this.getItemType(item)}`,
          );

          const mapKey = typeof item.id === 'string' && item.id.length > 0 ? item.id : randomUUID();
          let toolState = activeTools.get(mapKey);
          const latestInput = this.buildToolInputPayload(item);

          if (!toolState) {
            toolState = {
              toolCallId: mapKey,
              toolName,
              inputPayload: latestInput,
              hasEmittedCall: false,
            };
            activeTools.set(mapKey, toolState);
          } else {
            toolState.toolName = toolName;
            if (latestInput !== undefined) {
              toolState.inputPayload = latestInput;
            }
          }

          if (!toolState.hasEmittedCall) {
            this.logger.debug(`[codex-cli] Emitting tool invocation: ${toolState.toolName}`);
            this.emitToolInvocation(
              controller,
              toolState.toolCallId,
              toolState.toolName,
              toolState.inputPayload,
            );
            toolState.hasEmittedCall = true;
          }

          if (event.type === 'item.completed') {
            const { result, metadata } = this.buildToolResultPayload(item);
            this.logger.debug(`[codex-cli] Tool completed: ${toolState.toolName}`);
            this.emitToolResult(
              controller,
              toolState.toolCallId,
              toolState.toolName,
              item,
              result,
              metadata,
            );
            activeTools.delete(mapKey);
          }
        };

        // Abort support
        const onAbort = () => {
          child.kill('SIGTERM');
        };
        if (options.abortSignal) {
          if (options.abortSignal.aborted) {
            child.kill('SIGTERM');
            controller.error(options.abortSignal.reason ?? new Error('Request aborted'));
            return;
          }
          options.abortSignal.addEventListener('abort', onAbort, { once: true });
        }

        const finishStream = (code: number | null) => {
          const duration = Date.now() - startTime;

          if (code !== 0) {
            this.logger.error(
              `[codex-cli] Stream process exited with code ${code} after ${duration}ms`,
            );
            controller.error(
              createAPICallError({
                message: `Codex CLI exited with code ${code}`,
                exitCode: code ?? undefined,
                stderr,
                promptExcerpt,
              }),
            );
            return;
          }

          if (turnFailureMessage) {
            this.logger.error(`[codex-cli] Stream failed: ${turnFailureMessage}`);
            controller.error(
              createAPICallError({
                message: turnFailureMessage,
                stderr,
                promptExcerpt,
              }),
            );
            return;
          }

          // Emit text (non-streaming JSONL suppresses deltas; we send final text once)
          let finalText = accumulatedText;
          if (!finalText && lastMessagePath) {
            try {
              const fileText = readFileSync(lastMessagePath, 'utf8');
              if (fileText) finalText = fileText.trim();
            } catch {}
            if (lastMessageIsTemp) {
              try {
                rmSync(lastMessagePath, { force: true });
              } catch {}
            }
          }

          // No JSON extraction needed - native schema guarantees valid JSON
          if (finalText) {
            const textId = randomUUID();
            controller.enqueue({ type: 'text-start', id: textId });
            controller.enqueue({ type: 'text-delta', id: textId, delta: finalText });
            controller.enqueue({ type: 'text-end', id: textId });
          }

          const usageSummary = lastUsage ?? { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
          this.logger.info(
            `[codex-cli] Stream completed - Session: ${this.sessionId ?? 'N/A'}, Duration: ${duration}ms, Tokens: ${usageSummary.totalTokens}`,
          );
          this.logger.debug(
            `[codex-cli] Token usage - Input: ${usageSummary.inputTokens}, Output: ${usageSummary.outputTokens}, Total: ${usageSummary.totalTokens}`,
          );
          controller.enqueue({
            type: 'finish',
            finishReason: 'stop',
            usage: usageSummary,
          });
          controller.close();
        };

        child.stderr.on('data', (d) => (stderr += String(d)));
        child.stdout.setEncoding('utf8');
        child.stdout.on('data', (chunk: string) => {
          const lines = chunk.split(/\r?\n/).filter(Boolean);
          for (const line of lines) {
            const event = this.parseExperimentalJsonEvent(line);
            if (!event) continue;

            this.logger.debug(`[codex-cli] Stream event: ${event.type ?? 'unknown'}`);

            if (event.type === 'thread.started' && typeof event.thread_id === 'string') {
              this.sessionId = event.thread_id;
              this.logger.debug(`[codex-cli] Stream session started: ${this.sessionId}`);
              if (!responseMetadataSent) {
                responseMetadataSent = true;
                sendMetadata();
              }
              continue;
            }

            if (event.type === 'session.created' && typeof event.session_id === 'string') {
              this.sessionId = event.session_id;
              this.logger.debug(`[codex-cli] Stream session created: ${this.sessionId}`);
              if (!responseMetadataSent) {
                responseMetadataSent = true;
                sendMetadata();
              }
              continue;
            }

            if (event.type === 'turn.completed') {
              const usageEvent = this.extractUsage(event);
              if (usageEvent) {
                lastUsage = usageEvent;
              }
              continue;
            }

            if (event.type === 'turn.failed') {
              const errorText =
                (event.error && typeof event.error.message === 'string' && event.error.message) ||
                (typeof event.message === 'string' ? event.message : undefined);
              turnFailureMessage = errorText ?? turnFailureMessage ?? 'Codex turn failed';
              this.logger.error(`[codex-cli] Stream turn failed: ${turnFailureMessage}`);
              sendMetadata({ error: turnFailureMessage });
              continue;
            }

            if (event.type === 'error') {
              const errorText = typeof event.message === 'string' ? event.message : undefined;
              const effective = errorText ?? 'Codex error';
              turnFailureMessage = turnFailureMessage ?? effective;
              this.logger.error(`[codex-cli] Stream error event: ${effective}`);
              sendMetadata({ error: effective });
              continue;
            }

            if (event.type && event.type.startsWith('item.')) {
              handleItemEvent(event);
            }
          }
        });

        const cleanupSchema = () => {
          if (!schemaPath) return;
          try {
            const schemaDir = dirname(schemaPath);
            rmSync(schemaDir, { recursive: true, force: true });
          } catch {}
        };

        child.on('error', (e) => {
          this.logger.error(`[codex-cli] Stream spawn error: ${String(e)}`);
          if (options.abortSignal) options.abortSignal.removeEventListener('abort', onAbort);
          cleanupSchema();
          controller.error(this.handleSpawnError(e, promptExcerpt));
        });
        child.on('close', (code) => {
          if (options.abortSignal) options.abortSignal.removeEventListener('abort', onAbort);

          // Clean up temp schema file
          cleanupSchema();

          // Use setImmediate to ensure all stdout 'data' events are processed first
          setImmediate(() => finishStream(code));
        });
      },
      cancel: () => {},
    });

    return { stream, request: { body: promptText } } as Awaited<
      ReturnType<LanguageModelV2['doStream']>
    >;
  }
}


--- src/codex-cli-provider.ts ---
import type { LanguageModelV2, ProviderV2 } from '@ai-sdk/provider';
import { NoSuchModelError } from '@ai-sdk/provider';
import { CodexCliLanguageModel } from './codex-cli-language-model.js';
import type { CodexCliProviderSettings, CodexCliSettings } from './types.js';
import { getLogger } from './logger.js';
import { validateSettings } from './validation.js';

export interface CodexCliProvider extends ProviderV2 {
  (modelId: string, settings?: CodexCliSettings): LanguageModelV2;
  languageModel(modelId: string, settings?: CodexCliSettings): LanguageModelV2;
  chat(modelId: string, settings?: CodexCliSettings): LanguageModelV2;
  textEmbeddingModel(modelId: string): never;
  imageModel(modelId: string): never;
}

export function createCodexCli(options: CodexCliProviderSettings = {}): CodexCliProvider {
  const logger = getLogger(options.defaultSettings?.logger);

  if (options.defaultSettings) {
    const v = validateSettings(options.defaultSettings);
    if (!v.valid) {
      throw new Error(`Invalid default settings: ${v.errors.join(', ')}`);
    }
    for (const w of v.warnings) logger.warn(`Codex CLI Provider: ${w}`);
  }

  const createModel = (modelId: string, settings: CodexCliSettings = {}): LanguageModelV2 => {
    const merged: CodexCliSettings = { ...options.defaultSettings, ...settings };
    const v = validateSettings(merged);
    if (!v.valid) throw new Error(`Invalid settings: ${v.errors.join(', ')}`);
    for (const w of v.warnings) logger.warn(`Codex CLI: ${w}`);
    return new CodexCliLanguageModel({ id: modelId, settings: merged });
  };

  const provider = function (modelId: string, settings?: CodexCliSettings) {
    if (new.target) throw new Error('The Codex CLI provider function cannot be called with new.');
    return createModel(modelId, settings);
  } as CodexCliProvider;

  provider.languageModel = createModel;
  provider.chat = createModel;
  provider.textEmbeddingModel = ((modelId: string) => {
    throw new NoSuchModelError({ modelId, modelType: 'textEmbeddingModel' });
  }) as never;
  provider.imageModel = ((modelId: string) => {
    throw new NoSuchModelError({ modelId, modelType: 'imageModel' });
  }) as never;

  return provider;
}

export const codexCli = createCodexCli();


--- src/errors.ts ---
import { APICallError, LoadAPIKeyError } from '@ai-sdk/provider';

export interface CodexErrorMetadata {
  code?: string;
  exitCode?: number;
  stderr?: string;
  promptExcerpt?: string;
}

export function createAPICallError({
  message,
  code,
  exitCode,
  stderr,
  promptExcerpt,
  isRetryable = false,
}: CodexErrorMetadata & { message: string; isRetryable?: boolean }): APICallError {
  const data: CodexErrorMetadata = { code, exitCode, stderr, promptExcerpt };
  return new APICallError({
    message,
    isRetryable,
    url: 'codex-cli://exec',
    requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : undefined,
    data,
  });
}

export function createAuthenticationError(message?: string): LoadAPIKeyError {
  return new LoadAPIKeyError({
    message: message || 'Authentication failed. Ensure Codex CLI is logged in (codex login).',
  });
}

export function isAuthenticationError(err: unknown): boolean {
  if (err instanceof LoadAPIKeyError) return true;
  if (err instanceof APICallError) {
    const data = err.data as CodexErrorMetadata | undefined;
    if (data?.exitCode === 401) return true;
  }
  return false;
}


--- src/index.ts ---
export { createCodexCli, codexCli } from './codex-cli-provider.js';
export type { CodexCliProvider } from './codex-cli-provider.js';

export type {
  CodexCliSettings,
  CodexCliProviderSettings,
  CodexCliProviderOptions,
  Logger,
  ReasoningEffort,
  ReasoningSummary,
  ReasoningSummaryFormat,
  ModelVerbosity,
} from './types.js';

export { CodexCliLanguageModel } from './codex-cli-language-model.js';

// Error helpers
export { isAuthenticationError } from './errors.js';


--- src/logger.ts ---
import type { Logger } from './types.js';

/**
 * Default logger that uses console with level tags.
 */
/* eslint-disable no-console -- Default logger legitimately uses all console methods */
const defaultLogger: Logger = {
  debug: (message: string) => console.debug(`[DEBUG] ${message}`),
  info: (message: string) => console.info(`[INFO] ${message}`),
  warn: (message: string) => console.warn(`[WARN] ${message}`),
  error: (message: string) => console.error(`[ERROR] ${message}`),
};
/* eslint-enable no-console */

/**
 * No-op logger that discards all messages.
 */
const noopLogger: Logger = {
  debug: () => {},
  info: () => {},
  warn: () => {},
  error: () => {},
};

/**
 * Gets the appropriate logger based on configuration.
 *
 * @param logger - Logger configuration from settings
 * @returns The logger to use
 */
export function getLogger(logger: Logger | false | undefined): Logger {
  if (logger === false) {
    return noopLogger;
  }

  if (logger === undefined) {
    return defaultLogger;
  }

  return logger;
}

/**
 * Creates a verbose-aware logger that only logs debug/info when verbose is enabled.
 * Warn and error are always logged regardless of verbose setting.
 *
 * @param logger - Base logger to wrap
 * @param verbose - Whether to enable verbose (debug/info) logging
 * @returns Logger with verbose-aware behavior
 */
export function createVerboseLogger(logger: Logger, verbose: boolean = false): Logger {
  if (verbose) {
    // When verbose is enabled, use all log levels
    return logger;
  }

  // When verbose is disabled, only allow warn/error
  // Bind methods to preserve 'this' context for class-based loggers
  return {
    debug: () => {}, // No-op when not verbose
    info: () => {}, // No-op when not verbose
    warn: logger.warn.bind(logger),
    error: logger.error.bind(logger),
  };
}


--- src/message-mapper.ts ---
import type { ModelMessage } from 'ai';

type TextPart = { type: 'text'; text: string };
type ImagePart = { type: 'image'; [k: string]: unknown };
type ToolOutputText = { type: 'text'; value: string };
type ToolOutputJson = { type: 'json'; value: unknown };
type ToolItem = { toolName: string; output: ToolOutputText | ToolOutputJson };

function isTextPart(p: unknown): p is TextPart {
  return (
    typeof p === 'object' &&
    p !== null &&
    'type' in p &&
    (p as { type?: unknown }).type === 'text' &&
    'text' in p &&
    typeof (p as { text?: unknown }).text === 'string'
  );
}

function isImagePart(p: unknown): p is ImagePart {
  return (
    typeof p === 'object' && p !== null && 'type' in p && (p as { type?: unknown }).type === 'image'
  );
}

function isToolItem(p: unknown): p is ToolItem {
  if (typeof p !== 'object' || p === null) return false;
  const obj = p as { toolName?: unknown; output?: unknown };
  if (typeof obj.toolName !== 'string') return false;
  const out = obj.output as { type?: unknown; value?: unknown } | undefined;
  if (!out || (out.type !== 'text' && out.type !== 'json')) return false;
  if (out.type === 'text' && typeof out.value !== 'string') return false;
  return true;
}

export function mapMessagesToPrompt(prompt: readonly ModelMessage[]): {
  promptText: string;
  warnings?: string[];
} {
  const warnings: string[] = [];
  const parts: string[] = [];

  let systemText: string | undefined;

  for (const msg of prompt) {
    if (msg.role === 'system') {
      systemText = typeof msg.content === 'string' ? msg.content : String(msg.content);
      continue;
    }

    if (msg.role === 'user') {
      if (typeof msg.content === 'string') {
        parts.push(`Human: ${msg.content}`);
      } else if (Array.isArray(msg.content)) {
        const text = msg.content
          .filter(isTextPart)
          .map((p) => p.text)
          .join('\n');
        if (text) parts.push(`Human: ${text}`);
        const images = msg.content.filter(isImagePart);
        if (images.length) warnings.push('Image inputs ignored by Codex CLI integration.');
      }
      continue;
    }

    if (msg.role === 'assistant') {
      if (typeof msg.content === 'string') {
        parts.push(`Assistant: ${msg.content}`);
      } else if (Array.isArray(msg.content)) {
        const text = msg.content
          .filter(isTextPart)
          .map((p) => p.text)
          .join('\n');
        if (text) parts.push(`Assistant: ${text}`);
      }
      continue;
    }

    if (msg.role === 'tool') {
      if (Array.isArray(msg.content)) {
        for (const maybeTool of msg.content) {
          if (!isToolItem(maybeTool)) continue;
          const value =
            maybeTool.output.type === 'text'
              ? maybeTool.output.value
              : JSON.stringify(maybeTool.output.value);
          parts.push(`Tool Result (${maybeTool.toolName}): ${value}`);
        }
      }
      continue;
    }
  }

  let promptText = '';
  if (systemText) promptText += systemText + '\n\n';
  promptText += parts.join('\n\n');

  return { promptText, ...(warnings.length ? { warnings } : {}) };
}


--- src/types.ts ---
// Types and settings for Codex CLI provider

/**
 * Logger interface for custom logging.
 * Allows consumers to provide their own logging implementation
 * or disable logging entirely.
 *
 * @example
 * ```typescript
 * const customLogger: Logger = {
 *   debug: (message) => myLoggingService.debug(message),
 *   info: (message) => myLoggingService.info(message),
 *   warn: (message) => myLoggingService.warn(message),
 *   error: (message) => myLoggingService.error(message),
 * };
 * ```
 */
export interface Logger {
  /**
   * Log a debug message. Only logged when verbose mode is enabled.
   * Used for detailed execution tracing and troubleshooting.
   */
  debug: (message: string) => void;

  /**
   * Log an informational message. Only logged when verbose mode is enabled.
   * Used for general execution flow information.
   */
  info: (message: string) => void;

  /**
   * Log a warning message.
   */
  warn: (message: string) => void;

  /**
   * Log an error message.
   */
  error: (message: string) => void;
}

export type ApprovalMode = 'untrusted' | 'on-failure' | 'on-request' | 'never';

export type SandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access';

export type ReasoningEffort = 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
/**
 * Reasoning summary detail level.
 * Note: The API error messages claim 'concise' and 'none' are valid, but they are
 * actually rejected with 400 errors. Only 'auto' and 'detailed' work in practice.
 */
export type ReasoningSummary = 'auto' | 'detailed';
export type ReasoningSummaryFormat = 'none' | 'experimental';
export type ModelVerbosity = 'low' | 'medium' | 'high';

export interface McpServerBase {
  /**
   * Enable/disable this MCP server without removing its definition.
   * Maps to: `mcp_servers.<name>.enabled`
   */
  enabled?: boolean;

  /**
   * Time allowed for the MCP server to start (in seconds).
   * Maps to: `mcp_servers.<name>.startup_timeout_sec`
   */
  startupTimeoutSec?: number;

  /**
   * Max time a single MCP tool call may run (in seconds).
   * Maps to: `mcp_servers.<name>.tool_timeout_sec`
   */
  toolTimeoutSec?: number;

  /**
   * Explicit allow/deny lists for tools exposed by the server.
   * Maps to: `mcp_servers.<name>.enabled_tools` / `disabled_tools`
   */
  enabledTools?: string[];
  disabledTools?: string[];
}

export interface McpServerStdio extends McpServerBase {
  /** Execute an MCP server over stdio */
  transport: 'stdio';

  /** Command to start the MCP server (e.g., `node`, `python`, or a binary path). */
  command: string;

  /** Arguments passed to the command. */
  args?: string[];

  /** Environment variables passed to the MCP process. */
  env?: Record<string, string>;

  /** Optional working directory for the MCP server process. */
  cwd?: string;
}

export interface McpServerHttp extends McpServerBase {
  /** Use an HTTP-based MCP server (RMCP). */
  transport: 'http';

  /** Base URL for the MCP server. */
  url: string;

  /** Bearer token supplied inline (use env var variant to avoid embedding secrets). */
  bearerToken?: string;

  /** Name of env var that holds the bearer token. */
  bearerTokenEnvVar?: string;

  /** Static HTTP headers to send with each MCP request. */
  httpHeaders?: Record<string, string>;

  /** Names of env vars whose values should be sent as HTTP headers. */
  envHttpHeaders?: Record<string, string>;
}

export type McpServerConfig = McpServerStdio | McpServerHttp;

export interface CodexCliSettings {
  // Path to the codex CLI JS entry (bin/codex.js) or executable. If omitted, the provider tries to resolve @openai/codex.
  codexPath?: string;

  // Set working directory for the Codex process
  cwd?: string;

  // Additional directories Codex should be allowed to read/write (maps to repeated --add-dir)
  addDirs?: string[];

  // Approval policy for command execution
  approvalMode?: ApprovalMode;

  // Sandbox mode for command execution
  sandboxMode?: SandboxMode;

  // Convenience: fully auto (equivalent to --full-auto)
  fullAuto?: boolean;

  // Danger mode which bypasses approvals and sandbox (equivalent to --dangerously-bypass-approvals-and-sandbox)
  dangerouslyBypassApprovalsAndSandbox?: boolean;

  // Skip Git repo safety check (recommended for CI/non-repo usage)
  skipGitRepoCheck?: boolean;

  // Force color handling in Codex CLI output; defaults to auto
  color?: 'always' | 'never' | 'auto';

  // Allow falling back to `npx @openai/codex` if the binary cannot be resolved
  allowNpx?: boolean;

  // Optional: write last agent message to this file (Codex CLI flag)
  outputLastMessageFile?: string;

  // Extra environment variables for the spawned process (e.g., OPENAI_API_KEY)
  env?: Record<string, string>;

  // Enable verbose provider logging
  verbose?: boolean;

  // Custom logger; set to false to disable logging
  logger?: Logger | false;

  // ===== Reasoning & Verbosity =====

  /**
   * Controls reasoning effort for reasoning-capable models (o3, o4-mini, the GPT-5.1 family,
   * and legacy GPT-5 slugs). Higher effort produces more thorough reasoning at the cost of latency.
   *
   * Codex CLI model presets currently expose `low`/`medium`/`high` for `gpt-5.1` and `gpt-5.1-codex`.
   * `gpt-5.1-codex-max` additionally supports `xhigh`. `gpt-5.1-codex-mini` only offers `medium`/`high`.
   * The legacy `gpt-5` slug still allowed `minimal`, but GPT-5.1 rejects it.
   *
   * Maps to: `-c model_reasoning_effort=<value>`
   * @see https://platform.openai.com/docs/guides/reasoning
   */
  reasoningEffort?: ReasoningEffort;

  /**
   * Controls reasoning summary detail level.
   *
   * Valid values: 'auto' | 'detailed'
   * Note: Despite API error messages claiming 'concise' and 'none' are valid,
   * they are rejected with 400 errors in practice.
   *
   * Maps to: `-c model_reasoning_summary=<value>`
   * @see https://platform.openai.com/docs/guides/reasoning#reasoning-summaries
   */
  reasoningSummary?: ReasoningSummary;

  /**
   * Controls reasoning summary format (experimental).
   *
   * Maps to: `-c model_reasoning_summary_format=<value>`
   */
  reasoningSummaryFormat?: ReasoningSummaryFormat;

  /**
   * Controls output length/detail for GPT-5.1 (non-Codex) and legacy GPT-5 models.
   * Codex-specific slugs ignore this flag because the CLI disables verbosity for them.
   * Only applies to models using the Responses API.
   *
   * Maps to: `-c model_verbosity=<value>`
   */
  modelVerbosity?: ModelVerbosity;

  // ===== MCP configuration =====

  /**
   * Configure MCP servers (stdio or HTTP/RMCP). Keys are server names.
   * Each entry maps to the Codex CLI `mcp_servers.<name>` table.
   */
  mcpServers?: Record<string, McpServerConfig>;

  /**
   * Enable the RMCP client so HTTP-based MCP servers can be contacted.
   * Maps to: `-c features.rmcp_client=true`
   */
  rmcpClient?: boolean;

  // ===== Advanced Codex Features =====

  /**
   * Include experimental plan tool that the model can use to update its current plan.
   *
   * Maps to: `--include-plan-tool`
   */
  includePlanTool?: boolean;

  /**
   * Configuration profile from config.toml to specify default options.
   *
   * Maps to: `--profile <name>`
   */
  profile?: string;

  /**
   * Use OSS provider (experimental).
   *
   * Maps to: `--oss`
   */
  oss?: boolean;

  /**
   * Enable web search tool for the model.
   *
   * Maps to: `-c tools.web_search=true`
   */
  webSearch?: boolean;

  // ===== Generic config overrides (maps to -c key=value) =====

  /**
   * Generic Codex CLI config overrides. Allows setting any config value
   * without updating the provider.
   *
   * Each entry maps to: `-c <key>=<value>`
   *
   * Examples:
   * - `{ experimental_resume: '/tmp/session.jsonl' }`
   * - `{ 'model_providers.custom.base_url': 'http://localhost:8000' }`
   * - `{ 'sandbox_workspace_write': { network_access: true } }`
   *
   * Values are serialized:
   * - string → raw string
   * - number/boolean → String(value)
   * - plain objects → flattened recursively to dotted keys
   * - arrays → JSON.stringify(value)
   * - other objects (Date, RegExp, Map, etc.) → JSON.stringify(value)
   */
  configOverrides?: Record<string, string | number | boolean | object>;
}

export interface CodexCliProviderSettings {
  // Default settings applied to language models created by this provider
  defaultSettings?: CodexCliSettings;
}

/**
 * Per-call overrides supplied through AI SDK providerOptions.
 * These values take precedence over constructor-level CodexCliSettings.
 */
export interface CodexCliProviderOptions {
  /**
   * Per-call override for reasoning depth.
   * Maps to `model_reasoning_effort`.
   */
  reasoningEffort?: ReasoningEffort;

  /**
   * Per-call override for reasoning summary detail level.
   * Maps to `model_reasoning_summary`.
   */
  reasoningSummary?: ReasoningSummary;

  /**
   * Per-call override for reasoning summary format.
   * Maps to `model_reasoning_summary_format`.
   */
  reasoningSummaryFormat?: ReasoningSummaryFormat;

  /**
   * AI SDK naming for per-call verbosity overrides.
   * Maps to Codex `model_verbosity`.
   */
  textVerbosity?: ModelVerbosity;

  /**
   * Per-call override for extra directories Codex can access.
   * Maps to repeated `--add-dir` flags.
   */
  addDirs?: string[];

  /**
   * Per-call Codex CLI config overrides. These are merged with
   * constructor-level overrides with per-call values taking precedence.
   */
  configOverrides?: Record<string, string | number | boolean | object>;

  /**
   * Per-call MCP server definitions. Merged with constructor definitions
   * (per-call servers and fields take precedence).
   */
  mcpServers?: Record<string, McpServerConfig>;

  /**
   * Per-call override for RMCP client enablement.
   */
  rmcpClient?: boolean;
}


--- src/validation.ts ---
import { z } from 'zod';
import type { CodexCliSettings } from './types.js';

const loggerFunctionSchema = z.object({
  debug: z.any().refine((val) => typeof val === 'function', {
    message: 'debug must be a function',
  }),
  info: z.any().refine((val) => typeof val === 'function', {
    message: 'info must be a function',
  }),
  warn: z.any().refine((val) => typeof val === 'function', {
    message: 'warn must be a function',
  }),
  error: z.any().refine((val) => typeof val === 'function', {
    message: 'error must be a function',
  }),
});

const mcpServerBaseSchema = z.object({
  enabled: z.boolean().optional(),
  startupTimeoutSec: z.number().int().positive().optional(),
  toolTimeoutSec: z.number().int().positive().optional(),
  enabledTools: z.array(z.string()).optional(),
  disabledTools: z.array(z.string()).optional(),
});

const mcpServerStdioSchema = mcpServerBaseSchema.extend({
  transport: z.literal('stdio'),
  command: z.string().min(1),
  args: z.array(z.string()).optional(),
  env: z.record(z.string(), z.string()).optional(),
  cwd: z.string().optional(),
});

const mcpServerHttpSchema = mcpServerBaseSchema.extend({
  transport: z.literal('http'),
  url: z.string().min(1),
  bearerToken: z.string().optional(),
  bearerTokenEnvVar: z.string().optional(),
  httpHeaders: z.record(z.string(), z.string()).optional(),
  envHttpHeaders: z.record(z.string(), z.string()).optional(),
});

const mcpServerSchema = z.discriminatedUnion('transport', [
  mcpServerStdioSchema,
  mcpServerHttpSchema,
]);

export const mcpServersSchema = z.record(z.string(), mcpServerSchema);

const settingsSchema = z
  .object({
    codexPath: z.string().optional(),
    cwd: z.string().optional(),
    addDirs: z.array(z.string().min(1)).optional(),
    approvalMode: z.enum(['untrusted', 'on-failure', 'on-request', 'never']).optional(),
    sandboxMode: z.enum(['read-only', 'workspace-write', 'danger-full-access']).optional(),
    fullAuto: z.boolean().optional(),
    dangerouslyBypassApprovalsAndSandbox: z.boolean().optional(),
    skipGitRepoCheck: z.boolean().optional(),
    color: z.enum(['always', 'never', 'auto']).optional(),
    allowNpx: z.boolean().optional(),
    outputLastMessageFile: z.string().optional(),
    env: z.record(z.string(), z.string()).optional(),
    verbose: z.boolean().optional(),
    logger: z.union([z.literal(false), loggerFunctionSchema]).optional(),

    // NEW: Reasoning & Verbosity
    reasoningEffort: z.enum(['minimal', 'low', 'medium', 'high', 'xhigh']).optional(),
    // Note: API rejects 'concise' and 'none' despite error messages claiming they're valid
    reasoningSummary: z.enum(['auto', 'detailed']).optional(),
    reasoningSummaryFormat: z.enum(['none', 'experimental']).optional(),
    modelVerbosity: z.enum(['low', 'medium', 'high']).optional(),

    // NEW: Advanced features
    includePlanTool: z.boolean().optional(),
    profile: z.string().optional(),
    oss: z.boolean().optional(),
    webSearch: z.boolean().optional(),
    mcpServers: mcpServersSchema.optional(),
    rmcpClient: z.boolean().optional(),

    // NEW: Generic overrides
    configOverrides: z
      .record(
        z.string(),
        z.union([
          z.string(),
          z.number(),
          z.boolean(),
          z.object({}).passthrough(),
          z.array(z.any()),
        ]),
      )
      .optional(),
  })
  .strict();

export function validateSettings(settings: unknown): {
  valid: boolean;
  warnings: string[];
  errors: string[];
} {
  const warnings: string[] = [];
  const errors: string[] = [];

  const parsed = settingsSchema.safeParse(settings);
  if (!parsed.success) {
    // zod v3 => error.errors, zod v4 => error.issues
    type ZodIssueLike = { path?: (string | number)[]; message?: string };
    const raw = parsed.error as unknown;
    let issues: ZodIssueLike[] = [];
    if (raw && typeof raw === 'object') {
      const v4 = (raw as { issues?: unknown }).issues;
      const v3 = (raw as { errors?: unknown }).errors;
      if (Array.isArray(v4)) issues = v4 as ZodIssueLike[];
      else if (Array.isArray(v3)) issues = v3 as ZodIssueLike[];
    }
    for (const i of issues) {
      const path = Array.isArray(i?.path) ? i.path.join('.') : '';
      const message = i?.message || 'Invalid value';
      errors.push(`${path ? path + ': ' : ''}${message}`);
    }
    return { valid: false, warnings, errors };
  }

  const s = parsed.data as CodexCliSettings;
  if (s.fullAuto && s.dangerouslyBypassApprovalsAndSandbox) {
    warnings.push(
      'Both fullAuto and dangerouslyBypassApprovalsAndSandbox specified; fullAuto takes precedence.',
    );
  }

  // Note: Previously warned about reasoningSummary='none', but 'none' is now rejected
  // by the schema as an invalid value (only 'auto' and 'detailed' are accepted)

  return { valid: true, warnings, errors };
}

export function validateModelId(modelId: string): string | undefined {
  if (!modelId || modelId.trim() === '') return 'Model ID cannot be empty';
  // We don’t restrict model values here; Codex forwards to Responses API
  return undefined;
}


--- src/__tests__/codex-cli-language-model.test.ts ---
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { CodexCliLanguageModel } from '../codex-cli-language-model.js';
import { PassThrough } from 'node:stream';
import { EventEmitter } from 'node:events';
import { writeFileSync, mkdtempSync, readFileSync, existsSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

// Helper to create a mock spawn that emits JSONL events
function makeMockSpawn(lines: string[], exitCode = 0) {
  return vi.fn((_cmd: string, args: string[]) => {
    const child = new EventEmitter() as any;
    child.stdout = new PassThrough();
    child.stderr = new PassThrough();
    child.kill = vi.fn();

    // If our code passes --output-last-message <path>, write there too
    const idx = args.indexOf('--output-last-message');
    if (idx !== -1 && args[idx + 1]) {
      try {
        writeFileSync(args[idx + 1], 'Fallback last message\n');
      } catch {}
    }

    // emit lines asynchronously
    setTimeout(() => {
      for (const l of lines) child.stdout.write(l + '\n');
      child.stdout.end();
      child.emit('close', exitCode);
    }, 5);

    return child;
  });
}

// Mock child_process
vi.mock('node:child_process', async () => {
  let currentMock: (cmd: string, args: string[]) => any = makeMockSpawn([], 0) as any;
  const mod = {
    spawn: (cmd: string, args: string[]) => currentMock(cmd, args),
    __setSpawnMock: (fn: any) => {
      currentMock = fn;
    },
  } as any;
  return mod;
});

// Access the helper to swap mocks inside tests
const childProc = await import('node:child_process');

describe('CodexCliLanguageModel', () => {
  beforeEach(() => {
    vi.restoreAllMocks();
  });

  it('doGenerate returns text and sessionId from experimental JSON events', async () => {
    const lines = [
      JSON.stringify({
        type: 'thread.started',
        thread_id: 'thread-123',
      }),
      JSON.stringify({
        type: 'turn.completed',
        usage: { input_tokens: 10, output_tokens: 5 },
      }),
      JSON.stringify({
        type: 'item.completed',
        item: { item_type: 'assistant_message', text: 'Hello JSON' },
      }),
    ];
    (childProc as any).__setSpawnMock(makeMockSpawn(lines, 0));

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: { allowNpx: true, color: 'never' },
    });
    const res = await model.doGenerate({ prompt: [{ role: 'user', content: 'Say hi' }] as any });
    expect(res.content[0]).toMatchObject({ type: 'text', text: 'Hello JSON' });
    expect(res.providerMetadata?.['codex-cli']).toMatchObject({ sessionId: 'thread-123' });
    expect(res.usage).toMatchObject({ inputTokens: 10, outputTokens: 5, totalTokens: 15 });
  });

  it('doStream yields response-metadata, text-delta, finish', async () => {
    const lines = [
      JSON.stringify({ type: 'thread.started', thread_id: 'thread-1' }),
      JSON.stringify({
        type: 'item.completed',
        item: { item_type: 'assistant_message', text: 'Streamed hello' },
      }),
      JSON.stringify({ type: 'turn.completed', usage: { input_tokens: 0, output_tokens: 0 } }),
    ];
    (childProc as any).__setSpawnMock(makeMockSpawn(lines, 0));

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: { allowNpx: true, color: 'never' },
    });
    const { stream } = await model.doStream({
      prompt: [{ role: 'user', content: 'Say hi' }] as any,
    });

    const received: any[] = [];
    const _reader = (stream as any).getReader ? undefined : null; // ensure Web stream compat
    const rs = stream as ReadableStream<any>;
    const it = (rs as any)[Symbol.asyncIterator]();
    for await (const part of it) received.push(part);

    const types = received.map((p) => p.type);
    expect(types).toContain('response-metadata');
    expect(types).toContain('text-delta');
    expect(types).toContain('finish');
    const deltaPayload = received.find((p) => p.type === 'text-delta');
    expect(deltaPayload?.delta).toBe('Streamed hello');
  });

  it('streams tool events for command execution items', async () => {
    const lines = [
      JSON.stringify({ type: 'thread.started', thread_id: 'thread-tools' }),
      JSON.stringify({
        type: 'item.started',
        item: {
          id: 'item_0',
          item_type: 'command_execution',
          command: 'ls -la',
          aggregated_output: '',
          exit_code: null,
          status: 'in_progress',
        },
      }),
      JSON.stringify({
        type: 'item.completed',
        item: {
          id: 'item_0',
          item_type: 'command_execution',
          command: 'ls -la',
          aggregated_output: 'README.md\n',
          exit_code: 0,
          status: 'completed',
        },
      }),
      JSON.stringify({
        type: 'item.completed',
        item: { id: 'item_1', item_type: 'assistant_message', text: 'done' },
      }),
      JSON.stringify({
        type: 'turn.completed',
        usage: { input_tokens: 4, output_tokens: 2, cached_input_tokens: 1 },
      }),
    ];
    (childProc as any).__setSpawnMock(makeMockSpawn(lines, 0));

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: { allowNpx: true, color: 'never' },
    });
    const { stream } = await model.doStream({
      prompt: [{ role: 'user', content: 'List files' }] as any,
    });

    const received: any[] = [];
    const rs = stream as ReadableStream<any>;
    const iterator = (rs as any)[Symbol.asyncIterator]();
    for await (const part of iterator) received.push(part);

    const toolCall = received.find((p) => p.type === 'tool-call');
    expect(toolCall?.toolName).toBe('exec');
    expect(toolCall?.providerExecuted).toBe(true);
    expect(toolCall?.input).toContain('ls -la');

    const toolResult = received.find((p) => p.type === 'tool-result');
    expect(toolResult?.toolCallId).toBe(toolCall?.toolCallId);
    expect(toolResult?.result).toMatchObject({
      command: 'ls -la',
      aggregatedOutput: 'README.md\n',
      exitCode: 0,
      status: 'completed',
    });

    const finish = received.find((p) => p.type === 'finish');
    expect(finish?.usage).toEqual({
      inputTokens: 4,
      outputTokens: 2,
      totalTokens: 6,
      cachedInputTokens: 1,
    });
  });

  it('includes approval/sandbox flags and output-last-message; uses npx with allowNpx', async () => {
    let seen: any = { cmd: '', args: [] as string[] };
    const lines = [
      JSON.stringify({ type: 'thread.started', thread_id: 'thread-2' }),
      JSON.stringify({
        type: 'item.completed',
        item: { item_type: 'assistant_message', text: 'OK' },
      }),
    ];
    (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
      seen = { cmd, args };
      return makeMockSpawn(lines, 0)(cmd, args);
    });

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: {
        allowNpx: true,
        color: 'never',
        approvalMode: 'on-failure',
        sandboxMode: 'workspace-write',
        skipGitRepoCheck: true,
        outputLastMessageFile: join(mkdtempSync(join(tmpdir(), 'codex-test-')), 'last.txt'),
      },
    });

    await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

    expect(['npx', 'node']).toContain(seen.cmd);
    expect(seen.args).toContain('exec');
    expect(seen.args).toContain('--experimental-json');
    expect(seen.args).not.toContain('--json');
    expect(seen.args).toContain('-c');
    expect(seen.args).toContain('approval_policy=on-failure');
    expect(seen.args).toContain('sandbox_mode=workspace-write');
    expect(seen.args).toContain('--skip-git-repo-check');
    expect(seen.args).toContain('--output-last-message');
  });

  it('retains user-provided outputLastMessageFile when fallback is used', async () => {
    let outputPath = '';
    const lines = [JSON.stringify({ type: 'thread.started', thread_id: 'thread-last-user' })];
    (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
      const idx = args.indexOf('--output-last-message');
      outputPath = idx !== -1 ? args[idx + 1] : '';
      return makeMockSpawn(lines, 0)(cmd, args);
    });

    const dir = mkdtempSync(join(tmpdir(), 'codex-last-msg-user-'));
    const filePath = join(dir, 'last.txt');

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: {
        allowNpx: true,
        color: 'never',
        outputLastMessageFile: filePath,
      },
    });

    const res = await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

    expect(res.content[0]).toMatchObject({ type: 'text', text: 'Fallback last message' });
    expect(outputPath).toBe(filePath);
    expect(existsSync(filePath)).toBe(true);
    expect(readFileSync(filePath, 'utf8')).toContain('Fallback last message');
  });

  it('cleans up auto-created outputLastMessageFile after fallback', async () => {
    let outputPath = '';
    const lines = [JSON.stringify({ type: 'thread.started', thread_id: 'thread-last-auto' })];
    (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
      const idx = args.indexOf('--output-last-message');
      outputPath = idx !== -1 ? args[idx + 1] : '';
      return makeMockSpawn(lines, 0)(cmd, args);
    });

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: {
        allowNpx: true,
        color: 'never',
      },
    });

    const res = await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

    expect(res.content[0]).toMatchObject({ type: 'text', text: 'Fallback last message' });
    expect(outputPath).toBeTruthy();
    expect(existsSync(outputPath)).toBe(false);
  });

  it('sets isError for failed command execution', async () => {
    const lines = [
      JSON.stringify({ type: 'thread.started', thread_id: 'thread-fail' }),
      JSON.stringify({
        type: 'item.started',
        item: {
          id: 'item_fail',
          item_type: 'command_execution',
          command: 'false',
          aggregated_output: '',
          exit_code: null,
          status: 'in_progress',
        },
      }),
      JSON.stringify({
        type: 'item.completed',
        item: {
          id: 'item_fail',
          item_type: 'command_execution',
          command: 'false',
          aggregated_output: '',
          exit_code: 1,
          status: 'failed',
        },
      }),
      JSON.stringify({
        type: 'item.completed',
        item: { id: 'item_1', item_type: 'assistant_message', text: 'oops' },
      }),
      JSON.stringify({ type: 'turn.completed', usage: { input_tokens: 0, output_tokens: 0 } }),
    ];
    (childProc as any).__setSpawnMock(makeMockSpawn(lines, 0));

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: { allowNpx: true, color: 'never' },
    });
    const { stream } = await model.doStream({
      prompt: [{ role: 'user', content: 'fail please' }] as any,
    });

    const received: any[] = [];
    const rs = stream as ReadableStream<any>;
    for await (const part of (rs as any)[Symbol.asyncIterator]()) received.push(part);

    const toolResult = received.find((p) => p.type === 'tool-result');
    expect(toolResult?.isError).toBe(true);
    expect(toolResult?.toolName).toBe('exec');
    expect(toolResult?.result).toMatchObject({
      command: 'false',
      exitCode: 1,
      status: 'failed',
    });
  });

  it('uses --full-auto when specified and omits -c flags', async () => {
    let lastArgs: string[] = [];
    const lines = [
      JSON.stringify({
        id: '1',
        msg: {
          type: 'session_configured',
          session_id: 'sess-3',
          model: 'gpt-5',
          history_log_id: 0,
          history_entry_count: 0,
        },
      }),
      JSON.stringify({ id: '2', msg: { type: 'task_complete', last_agent_message: 'OK' } }),
    ];
    (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
      lastArgs = args;
      return makeMockSpawn(lines, 0)(cmd, args);
    });

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: { allowNpx: true, fullAuto: true, color: 'never' },
    });
    await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

    expect(lastArgs).toContain('--full-auto');
    // No -c flags when fullAuto
    expect(lastArgs.join(' ')).not.toMatch(/approval_policy|sandbox_mode/);
  });

  it('rejects with APICallError on non-zero exit', async () => {
    (childProc as any).__setSpawnMock(makeMockSpawn([], 2));
    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: { allowNpx: true, color: 'never' },
    });
    await expect(
      model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any }),
    ).rejects.toMatchObject({
      name: 'AI_APICallError',
      data: { exitCode: 2 },
    });
  });

  // Note: auth error mapping is covered via error helpers; CLI error path requires real process semantics.

  it('propagates pre-aborted signal reason and kills child', async () => {
    let killed = false;
    (childProc as any).__setSpawnMock((_cmd: string, _args: string[]) => {
      const child = new EventEmitter() as any;
      child.stdout = new PassThrough();
      child.stderr = new PassThrough();
      child.kill = vi.fn(() => {
        killed = true;
      });
      return child;
    });

    const model = new CodexCliLanguageModel({
      id: 'gpt-5',
      settings: { allowNpx: true, color: 'never' },
    });
    const ac = new AbortController();
    const reason = new Error('aborted');
    ac.abort(reason);
    await expect(
      model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        abortSignal: ac.signal,
      }),
    ).rejects.toBe(reason);
    expect(killed).toBe(true);
  });

  describe('Phase 1: constructor-level parameters', () => {
    it('emits -c model_reasoning_* and model_verbosity args when set', async () => {
      let seen: any = { args: [] as string[] };
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-reason' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        seen = { args };
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5-codex',
        settings: {
          allowNpx: true,
          color: 'never',
          reasoningEffort: 'high',
          reasoningSummary: 'detailed',
          reasoningSummaryFormat: 'experimental',
          modelVerbosity: 'low',
        },
      });
      await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

      const a = seen.args as string[];
      expect(a).toContain('-c');
      expect(a).toContain('model_reasoning_effort=high');
      expect(a).toContain('model_reasoning_summary=detailed');
      expect(a).toContain('model_reasoning_summary_format=experimental');
      expect(a).toContain('model_verbosity=low');
    });

    it('emits advanced feature flags (--include-plan-tool, --profile, --oss) and webSearch config', async () => {
      let seen: any = { args: [] as string[] };
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-adv' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        seen = { args };
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          includePlanTool: true,
          profile: 'production',
          oss: true,
          webSearch: true,
          color: 'never',
        },
      });
      await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

      const a = seen.args as string[];
      expect(a).toContain('--include-plan-tool');
      expect(a).toContain('--profile');
      expect(a).toContain('production');
      expect(a).toContain('--oss');
      expect(a).toContain('tools.web_search=true');
    });

    it('emits MCP stdio server config and rmcp client toggle', async () => {
      let captured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-mcp' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        captured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          rmcpClient: true,
          mcpServers: {
            files: {
              transport: 'stdio',
              command: 'node',
              args: ['mcp.js'],
              env: { API_KEY: 'abc' },
              cwd: '/tmp/mcp',
              enabled: true,
              startupTimeoutSec: 5,
              toolTimeoutSec: 15,
              enabledTools: ['list'],
              disabledTools: ['write'],
            },
          },
        },
      });

      await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

      expect(captured).toContain('features.rmcp_client=true');
      expect(captured).toContain('mcp_servers.files.command=node');
      expect(captured).toContain('mcp_servers.files.args=["mcp.js"]');
      expect(captured).toContain('mcp_servers.files.env.API_KEY=abc');
      expect(captured).toContain('mcp_servers.files.cwd=/tmp/mcp');
      expect(captured).toContain('mcp_servers.files.enabled=true');
      expect(captured).toContain('mcp_servers.files.startup_timeout_sec=5');
      expect(captured).toContain('mcp_servers.files.tool_timeout_sec=15');
      expect(captured).toContain('mcp_servers.files.enabled_tools=["list"]');
      expect(captured).toContain('mcp_servers.files.disabled_tools=["write"]');
    });

    it('emits --add-dir for each additional directory', async () => {
      let captured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-add-dir' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        captured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          addDirs: ['../shared', '/tmp/lib'],
        },
      });

      await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

      const addDirFlags = captured.filter((v) => v === '--add-dir');
      expect(addDirFlags).toHaveLength(2);
      expect(captured).toContain('../shared');
      expect(captured).toContain('/tmp/lib');
    });

    it('emits -c for configOverrides with string, number, boolean, and object', async () => {
      let seen: any = { args: [] as string[] };
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-over' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        seen = { args };
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          configOverrides: {
            experimental_resume: '/tmp/session.jsonl',
            hide_agent_reasoning: true,
            model_context_window: 200000,
            sandbox_workspace_write: { network_access: true },
          },
        },
      });
      await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

      const a = seen.args as string[];
      expect(a).toContain('experimental_resume=/tmp/session.jsonl');
      expect(a).toContain('hide_agent_reasoning=true');
      expect(a).toContain('model_context_window=200000');
      expect(a).toContain('sandbox_workspace_write.network_access=true');
    });

    it('handles deep nesting, arrays, and dotted keys in configOverrides', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-over-2' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          configOverrides: {
            deep: { nested: { value: true } },
            arr: [1, 2],
            'dotted.key': 'val',
          },
        },
      });
      await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

      expect(argsCaptured).toContain('deep.nested.value=true');
      expect(argsCaptured).toContain('arr=[1,2]');
      expect(argsCaptured).toContain('dotted.key=val');
    });

    it('keeps reasoning flags when fullAuto is enabled (but omits approval/sandbox overrides)', async () => {
      let lastArgs: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-fa' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        lastArgs = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: { allowNpx: true, color: 'never', fullAuto: true, reasoningEffort: 'medium' },
      });
      await model.doGenerate({ prompt: [{ role: 'user', content: 'Hi' }] as any });

      expect(lastArgs).toContain('--full-auto');
      expect(lastArgs.join(' ')).not.toMatch(/approval_policy|sandbox_mode/);
      expect(lastArgs).toContain('model_reasoning_effort=medium');
    });
  });

  describe('Phase 2: providerOptions overrides', () => {
    it('overrides constructor reasoning settings per call', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-phase2' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5-codex',
        settings: {
          allowNpx: true,
          color: 'never',
          reasoningEffort: 'low',
        },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            reasoningEffort: 'high',
            reasoningSummary: 'detailed',
          },
        },
      });

      expect(argsCaptured).toContain('model_reasoning_effort=high');
      expect(argsCaptured.join(' ')).not.toContain('model_reasoning_effort=low');
      expect(argsCaptured).toContain('model_reasoning_summary=detailed');
    });

    it('merges configOverrides with per-call overrides taking precedence', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-config' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          configOverrides: {
            setting1: 'value1',
            setting2: 'value2',
          },
        },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            configOverrides: {
              setting2: 'override',
              setting3: 'value3',
            },
          },
        },
      });

      expect(argsCaptured).toContain('setting1=value1');
      expect(argsCaptured).toContain('setting2=override');
      expect(argsCaptured.join(' ')).not.toContain('setting2=value2');
      expect(argsCaptured).toContain('setting3=value3');
    });

    it('merges MCP servers across constructor and providerOptions', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-mcp-merge' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          mcpServers: {
            local: {
              transport: 'stdio',
              command: 'node',
              args: ['base.js'],
              env: { BASE: '1' },
            },
          },
        },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            rmcpClient: true,
            mcpServers: {
              local: {
                transport: 'stdio',
                command: 'node',
                args: ['override.js'],
                env: { EXTRA: '2' },
              },
              remote: {
                transport: 'http',
                url: 'https://mcp.example',
                bearerTokenEnvVar: 'MCP_TOKEN',
                httpHeaders: { 'x-debug': '1' },
              },
            },
          },
        },
      });

      expect(argsCaptured).toContain('features.rmcp_client=true');
      expect(argsCaptured).toContain('mcp_servers.local.command=node');
      expect(argsCaptured).toContain('mcp_servers.local.args=["override.js"]');
      expect(argsCaptured).toContain('mcp_servers.local.env.BASE=1');
      expect(argsCaptured).toContain('mcp_servers.local.env.EXTRA=2');
      expect(argsCaptured).toContain('mcp_servers.remote.url=https://mcp.example');
      expect(argsCaptured).toContain('mcp_servers.remote.bearer_token_env_var=MCP_TOKEN');
      expect(argsCaptured).toContain('mcp_servers.remote.http_headers.x-debug=1');
    });

    it('allows clearing stdio MCP args and tool lists with empty arrays', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-mcp-empty' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          mcpServers: {
            local: {
              transport: 'stdio',
              command: 'node',
              args: ['base.js'],
              enabledTools: ['one'],
              disabledTools: ['two'],
            },
          },
        },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            mcpServers: {
              local: {
                transport: 'stdio',
                command: 'node',
                args: [],
                enabledTools: [],
                disabledTools: [],
              },
            },
          },
        },
      });

      expect(argsCaptured).toContain('mcp_servers.local.args=[]');
      expect(argsCaptured).toContain('mcp_servers.local.enabled_tools=[]');
      expect(argsCaptured).toContain('mcp_servers.local.disabled_tools=[]');
    });

    it('allows clearing HTTP MCP headers with empty objects', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-mcp-http-empty' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          mcpServers: {
            remote: {
              transport: 'http',
              url: 'https://base.example',
              httpHeaders: { 'x-base': '1' },
              envHttpHeaders: { BASE_ENV: 'BASE_ENV' },
            },
          },
        },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            mcpServers: {
              remote: {
                transport: 'http',
                url: 'https://base.example',
                httpHeaders: {},
                envHttpHeaders: {},
              },
            },
          },
        },
      });

      expect(argsCaptured).toContain('mcp_servers.remote.http_headers={}');
      expect(argsCaptured).toContain('mcp_servers.remote.env_http_headers={}');
    });

    it('clears base bearerToken when overriding with bearerTokenEnvVar (auth bundle replacement)', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-auth-bundle' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: {
          allowNpx: true,
          color: 'never',
          mcpServers: {
            remote: {
              transport: 'http',
              url: 'https://api.example.com',
              bearerToken: 'base-token-secret', // Should be cleared
            },
          },
        },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            mcpServers: {
              remote: {
                transport: 'http',
                url: 'https://api.example.com',
                bearerTokenEnvVar: 'NEW_ENV_VAR', // Should replace the token
              },
            },
          },
        },
      });

      // Should contain the new env var
      expect(argsCaptured).toContain('mcp_servers.remote.bearer_token_env_var=NEW_ENV_VAR');
      // Should NOT contain the old token
      expect(argsCaptured.some((arg) => arg.includes('mcp_servers.remote.bearer_token=base-token-secret'))).toBe(false);
    });

    it('merges addDirs from providerOptions with constructor settings', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-add-dir-override' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: { allowNpx: true, color: 'never', addDirs: ['./base'] },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            addDirs: ['../feature'],
          },
        },
      });

      const addDirFlags = argsCaptured.filter((v) => v === '--add-dir');
      expect(addDirFlags).toHaveLength(2);
      expect(argsCaptured).toContain('./base');
      expect(argsCaptured).toContain('../feature');
    });

    it('maps textVerbosity provider option to model_verbosity flag', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-verbosity' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'ok' },
        }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: { allowNpx: true, color: 'never' },
      });

      await model.doGenerate({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            textVerbosity: 'high',
          },
        },
      });

      expect(argsCaptured).toContain('model_verbosity=high');
    });

    it('applies providerOptions during streaming calls', async () => {
      let argsCaptured: string[] = [];
      const lines = [
        JSON.stringify({ type: 'thread.started', thread_id: 'thread-stream' }),
        JSON.stringify({
          type: 'item.completed',
          item: { item_type: 'assistant_message', text: 'stream ok' },
        }),
        JSON.stringify({ type: 'turn.completed', usage: { input_tokens: 0, output_tokens: 0 } }),
      ];
      (childProc as any).__setSpawnMock((cmd: string, args: string[]) => {
        argsCaptured = args;
        return makeMockSpawn(lines, 0)(cmd, args);
      });

      const model = new CodexCliLanguageModel({
        id: 'gpt-5',
        settings: { allowNpx: true, color: 'never' },
      });

      const { stream } = await model.doStream({
        prompt: [{ role: 'user', content: 'Hi' }] as any,
        providerOptions: {
          'codex-cli': {
            reasoningEffort: 'medium',
            textVerbosity: 'low',
          },
        },
      });

      const reader = (stream as any)[Symbol.asyncIterator]();
      for await (const _ of reader) {
        // exhaust stream to ensure spawn completes
      }

      expect(argsCaptured).toContain('model_reasoning_effort=medium');
      expect(argsCaptured).toContain('model_verbosity=low');
    });
  });
});


--- src/__tests__/codex-cli-provider.test.ts ---
import { describe, it, expect } from 'vitest';
import { createCodexCli } from '../codex-cli-provider.js';

describe('createCodexCli', () => {
  it('creates a model with merged defaults', () => {
    const provider = createCodexCli({ defaultSettings: { skipGitRepoCheck: true } });
    const model: any = provider('gpt-5', { color: 'never' });
    expect(model.provider).toBe('codex-cli');
    expect(model.modelId).toBe('gpt-5');
  });

  it('accepts addDirs in defaultSettings', () => {
    const provider = createCodexCli({
      defaultSettings: { addDirs: ['../shared', '/tmp/lib'] },
    });
    const model: any = provider('gpt-5');
    expect(model.provider).toBe('codex-cli');
    expect(model.modelId).toBe('gpt-5');
  });

  it('accepts addDirs in per-model settings', () => {
    const provider = createCodexCli();
    const model: any = provider('gpt-5', { addDirs: ['../shared'] });
    expect(model.provider).toBe('codex-cli');
    expect(model.modelId).toBe('gpt-5');
  });

  it('accepts outputLastMessageFile in settings', () => {
    const provider = createCodexCli();
    const model: any = provider('gpt-5', { outputLastMessageFile: '/tmp/last.txt' });
    expect(model.provider).toBe('codex-cli');
    expect(model.modelId).toBe('gpt-5');
  });
});


## Links discovered
- [LIMITATIONS.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/LIMITATIONS.md)
- [LIMITATIONS.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/../../LIMITATIONS.md)
- [full documentation](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-codex-cli/guide.md)
- [GitHub](https://github.com/ben-vargas/ai-sdk-provider-codex-cli/issues)
- [CHANGELOG](https://github.com/AcidicSoil/llms-txt-registry/blob/main/CHANGELOG.md)
- [full documentation](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/./guide.md)
- [CHANGELOG](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/docs/ai-sdk-v5/../../CHANGELOG.md)
- [LIMITATIONS.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/LIMITATIONS.md)
- [LIMITATIONS.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main/examples/../LIMITATIONS.md)
- [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
- [![npm version](https://img.shields.io/npm/v/ai-sdk-provider-codex-cli.svg)
- [![npm downloads](https://img.shields.io/npm/dm/ai-sdk-provider-codex-cli.svg)
- [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)
- [Node >= 18](https://img.shields.io/badge/node-%3E%3D18-43853d?logo=node.js&logoColor=white)
- [AI SDK v5](https://img.shields.io/badge/AI%20SDK-v5-000?logo=vercel&logoColor=white)
- [Modules: ESM + CJS](https://img.shields.io/badge/modules-ESM%20%2B%20CJS-3178c6)
- [TypeScript](https://img.shields.io/badge/TypeScript-blue)
- [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)
- [![Latest Release](https://img.shields.io/github/v/release/ben-vargas/ai-sdk-provider-codex-cli?display_name=tag)
- [CHANGELOG](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-codex-cli/CHANGELOG.md)
- [docs/ai-sdk-v5/guide.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-codex-cli/docs/ai-sdk-v5/guide.md)
- [docs/ai-sdk-v5/configuration.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-codex-cli/docs/ai-sdk-v5/configuration.md)
- [docs/ai-sdk-v5/troubleshooting.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-codex-cli/docs/ai-sdk-v5/troubleshooting.md)
- [docs/ai-sdk-v5/limitations.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-codex-cli/docs/ai-sdk-v5/limitations.md)
- [examples/](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-codex-cli/examples.md)
- [LIMITATIONS.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-codex-cli/LIMITATIONS.md)
- [CHANGELOG](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main//CHANGELOG.md)
- [docs/ai-sdk-v5/guide.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main//docs/ai-sdk-v5/guide.md)
- [docs/ai-sdk-v5/configuration.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main//docs/ai-sdk-v5/configuration.md)
- [docs/ai-sdk-v5/troubleshooting.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main//docs/ai-sdk-v5/troubleshooting.md)
- [docs/ai-sdk-v5/limitations.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main//docs/ai-sdk-v5/limitations.md)
- [examples/](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main//examples/)
- [LIMITATIONS.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-codex-cli/main//LIMITATIONS.md)

--- docs/ai-sdk-provider-gemini-cli/ai-sdk-provider-gemini-cli-llms.txt ---
# Ai Sdk Provider Gemini Cli

> This project provides a stable, community-maintained TypeScript SDK provider for Google’s Gemini AI models, compatible with both Vercel AI SDK v4 and v5. Its purpose is to simplify integration of Gemini’s capabilities — including text generation, structured output via Zod, streaming, and tool calling — into applications using the AI SDK. It ensures compatibility with breaking changes, supports OAuth and API key authentication, and includes detailed documentation, examples, and logging for debugging.

**Remember:**
- AI SDK Provider
- Vercel AI SDK (v4 and v5)
- Google Gemini CLI Core
- OAuth Authentication
- Zod Schema Validation
- Streaming Responses

## Docs
- [README](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/README.md): install & quickstart.
- [Dependency Notes](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/dependency-notes.md): docs page.
- [Gemini Cli Auth Options](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/gemini-cli-auth-options.md): docs page.
- [Language Model V2 Implementation](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/language-model-v2-implementation.md): docs page.
- [Project Structure](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/project-structure.md): docs page.
- [Tool Schema Mapping](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/tool-schema-mapping.md): docs page.
- [Zod To Gemini Mapping](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/zod-to-gemini-mapping.md): docs page.
- [Breaking Changes](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/BREAKING_CHANGES.md): docs page.
- [Development Status](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/DEVELOPMENT_STATUS.md): docs page.
- [Guide](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/GUIDE.md): docs page.

## Tutorials
- [README](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/examples/README.md): install & quickstart.

## Optional
- [Changelog](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/CHANGELOG.md): version history.
- [Contributing](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/CONTRIBUTING.md): docs page.
- [README](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/README.md): docs page.
- [Eslint.Config](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/eslint.config.js): docs page.
- [Tsup.Config](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/tsup.config.ts): docs page.
- [Vitest.Config](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/vitest.config.ts): docs page.

## Src
- [Client](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/client.ts): docs page.
- [Error](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/error.ts): docs page.
- [Gemini Language Model](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/gemini-language-model.ts): docs page.
- [Gemini Provider](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/gemini-provider.ts): docs page.
- [Index](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/index.ts): docs page.
- [Logger](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/logger.ts): docs page.
- [Message Mapper](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/message-mapper.ts): docs page.
- [Tool Mapper](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/tool-mapper.ts): docs page.
- [Types](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/types.ts): docs page.
- [Validation](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/validation.ts): docs page.


## Links discovered
- [README](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/README.md)
- [Dependency Notes](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/dependency-notes.md)
- [Gemini Cli Auth Options](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/gemini-cli-auth-options.md)
- [Language Model V2 Implementation](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/language-model-v2-implementation.md)
- [Project Structure](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/project-structure.md)
- [Tool Schema Mapping](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/tool-schema-mapping.md)
- [Zod To Gemini Mapping](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/zod-to-gemini-mapping.md)
- [Breaking Changes](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/BREAKING_CHANGES.md)
- [Development Status](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/DEVELOPMENT_STATUS.md)
- [Guide](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/GUIDE.md)
- [README](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/examples/README.md)
- [Changelog](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/CHANGELOG.md)
- [Contributing](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/CONTRIBUTING.md)
- [README](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/README.md)
- [Eslint.Config](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/eslint.config.js)
- [Tsup.Config](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/tsup.config.ts)
- [Vitest.Config](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/vitest.config.ts)
- [Client](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/client.ts)
- [Error](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/error.ts)
- [Gemini Language Model](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/gemini-language-model.ts)
- [Gemini Provider](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/gemini-provider.ts)
- [Index](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/index.ts)
- [Logger](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/logger.ts)
- [Message Mapper](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/message-mapper.ts)
- [Tool Mapper](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/tool-mapper.ts)
- [Types](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/types.ts)
- [Validation](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/src/validation.ts)

--- docs/ai-sdk-provider-gemini-cli/ai-sdk-provider-gemini-cli-llms-ctx.txt ---
<project title="Ai Sdk Provider Gemini Cli" summary="This project provides a stable, community-maintained TypeScript SDK provider for Google’s Gemini AI models, compatible with both Vercel AI SDK v4 and v5. Its purpose is to simplify integration of Gemini’s capabilities — including text generation, structured output via Zod, streaming, and tool calling — into applications using the AI SDK. It ensures compatibility with breaking changes, supports OAuth and API key authentication, and includes detailed documentation, examples, and logging for debugging.">**Remember:**
- AI SDK Provider
- Vercel AI SDK (v4 and v5)
- Google Gemini CLI Core
- OAuth Authentication
- Zod Schema Validation
- Streaming Responses<docs><doc title="README" desc="install &amp; quickstart."># AI SDK Provider for Gemini CLI - Technical Documentation

This directory contains technical documentation for the AI SDK Provider for Gemini CLI implementation.

## AI SDK v5 Documentation

This provider is compatible with Vercel AI SDK v5. For v5-specific documentation, see:

- **[ai-sdk-v5/GUIDE.md](ai-sdk-v5/GUIDE.md)** - Comprehensive usage guide for v5
- **[ai-sdk-v5/BREAKING_CHANGES.md](ai-sdk-v5/BREAKING_CHANGES.md)** - Breaking changes and migration guide from v4
- **[ai-sdk-v5/TROUBLESHOOTING.md](ai-sdk-v5/TROUBLESHOOTING.md)** - Common issues and solutions for v5

## Documentation Overview

### 1. [Project Structure](./project-structure.md)
Complete codebase map showing the organization of source files, examples, and documentation.

### 2. [Authentication Options](./gemini-cli-auth-options.md)
Comprehensive guide to the three authentication methods supported by `@google/gemini-cli-core`:
- OAuth with Google Personal Account (`oauth-personal`)
- Gemini API Key (`gemini-api-key`)
- Vertex AI (`vertex-ai`)

### 3. [Language Model V2 Implementation](./language-model-v2-implementation.md)
Detailed specification of the Vercel AI SDK Language Model V2 interface implementation for v5:
- Core interfaces and types
- Message format specifications
- Tool calling interfaces
- Implementation patterns

### 4. [Tool Schema Mapping](./tool-schema-mapping.md)
Guide for mapping between Vercel AI SDK's tool schemas and Gemini's FunctionDeclaration format:
- Type mapping tables
- Implementation approach
- Unsupported features
- Testing considerations

### 5. [Zod to Gemini Mapping](./zod-to-gemini-mapping.md)
Comprehensive mapping between Zod schemas and Gemini's Schema format:
- Type conversions
- Constraint mappings
- Special case handling
- Implementation guidelines

## Quick Reference

### Authentication Setup
```typescript
// OAuth (default)
const gemini = createGeminiProvider({
  authType: 'oauth-personal'
});

// API Key
const gemini = createGeminiProvider({
  authType: 'gemini-api-key',
  apiKey: process.env.GEMINI_API_KEY
});
```

### Supported Models
- `gemini-3-pro-preview` - Latest next-generation model (Preview)
- `gemini-2.5-pro` - Previous generation production-ready model (64K output tokens)
- `gemini-2.5-flash` - Faster, efficient model (64K output tokens)

### Key Features
- ✅ Text generation and streaming
- ✅ System instructions
- ✅ Object generation with Zod schemas
- ✅ Tool calling (function calls)
- ✅ Multimodal inputs (text and images)
- ✅ Conversation history
- ✅ Abort signal support

## Architecture Notes

The provider implements a direct integration with Google's Cloud Code endpoints through the `@google/gemini-cli-core` library. This ensures:
- Native OAuth support with cached credentials
- Direct access to Gemini models
- Optimal performance without intermediate layers
- Full compatibility with Vercel AI SDK v5 patterns

For implementation examples, see the [examples directory](../examples/).</doc><doc title="Dependency Notes" desc="docs page."># Dependency Version Pinning Notes

## @google/gemini-cli-core

**Current Version:** 0.1.22 (EXACT - no caret or tilde)

### Why Exact Version Pinning?

The `@google/gemini-cli-core` package has been introducing breaking changes in patch versions, which violates semantic versioning principles. Examples of breaking changes observed:

### Breaking Changes Timeline

| Version | Release Date | Breaking Changes |
|---------|-------------|------------------|
| 0.1.12 | 2025-07-13 | Baseline version |
| 0.1.13 | 2025-07-19 | Unknown - worked with original code |
| 0.1.14 | 2025-07-25 | Potential breaking changes introduced |
| 0.1.15 | 2025-07-30 | - |
| 0.1.16 | 2025-08-02 | - |
| 0.1.17 | 2025-08-05 | - |
| 0.1.18 | 2025-08-06 | - |
| 0.1.19 | 2025-08-12 | - |
| 0.1.20 | 2025-08-13 | - |
| 0.1.21 | 2025-08-14 | Added telemetry tracking (session events, install IDs) |
| 0.1.22 | 2025-08-18 | Added session ID support |

### Specific Breaking Changes (0.1.13 → 0.1.22)

1. **Config Object Requirements:**
   - Added required `getUsageStatisticsEnabled()` method to config object
   - This method is used for telemetry control (introduced around v0.1.21)

2. **ContentGenerator Method Signatures:**
   - `generateContent()` now requires `userPromptId: string` as second parameter
   - `generateContentStream()` now requires `userPromptId: string` as second parameter
   - These are used for API request logging and telemetry

3. **Factory Function Changes:**
   - `createContentGenerator()` now accepts optional third parameter `sessionId`
   - Used for session tracking (added in v0.1.22)

### Evidence from Source Code

From `google-gemini/gemini-cli` repository, the current implementation shows:
```typescript
// Method calls now require prompt_id
contentGenerator.generateContent(request, prompt_id)
contentGenerator.generateContentStream(request, prompt_id)
```

These changes were made without incrementing the minor or major version, violating semantic versioning where:
- Patch versions (0.0.X) should only contain backwards-compatible bug fixes
- Minor versions (0.X.0) should contain backwards-compatible functionality
- Major versions (X.0.0) should contain breaking changes

### Version Compatibility Matrix

| ai-sdk-provider-gemini-cli | @google/gemini-cli-core | Status |
|---------------------------|------------------------|---------|
| 0.1.0 - 0.1.1            | ~0.1.13                | ❌ Broken with 0.1.22 |
| 0.1.2+                   | 0.1.22 (exact)         | ✅ Working |
| 1.0.0+                   | 0.1.21                 | ❌ Missing 0.1.22 fixes |
| 1.1.0+                   | 0.1.22 (exact)         | ✅ Working |

### Upgrade Strategy

Before upgrading `@google/gemini-cli-core`:

1. Review the changelog for breaking changes (if available)
2. Test thoroughly with the new version
3. Update our code to handle any breaking changes
4. Update this document with new compatibility information
5. Consider maintaining multiple versions if needed for backward compatibility

### Current Implementation: Hybrid Approach

We've implemented a robust hybrid solution that protects against future breaking changes:

#### Phase 1: Core Safety Methods ✅
- Implemented 14 commonly-used config methods with safe defaults
- Covers telemetry, session, debug, and file handling methods
- Provides immediate protection against known breaking changes

#### Phase 2: Proxy Safety Net ✅
- Proxy wrapper catches ALL unknown method calls
- Returns intelligent defaults based on method naming patterns
- Prevents runtime errors from missing methods

#### Phase 3: Debug Logging ✅
- Set `DEBUG=true` environment variable to log unknown method calls
- Helps identify which methods are actually used in practice
- Guides future implementation decisions

### How the Proxy Works

```typescript
// Unknown methods are caught and handled gracefully:
config.getSomeNewMethod() // Returns safe default, logs if DEBUG=true

// Smart defaults based on naming patterns:
- is* methods → false (boolean checks)
- has* methods → false (capability checks)
- get*Enabled/get*Mode methods → false
- get*Registry/get*Client/get*Service methods → undefined  
- get*Config/get*Options methods → {}
- get*Command/get*Path methods → undefined
- All others → undefined
```

### OAuth-Specific Methods

The config includes critical OAuth methods required for LOGIN_WITH_GOOGLE authentication:
- `isBrowserLaunchSuppressed()` → returns `false` (allows browser launch for OAuth flow)

### Benefits

1. **Future-proof**: New methods in gemini-cli-core won't break the integration
2. **Observable**: Debug logging shows what's actually being called
3. **Maintainable**: Only implement methods that are actually used
4. **Safe**: All unknown methods return appropriate defaults

### Recommendation

Until Google/Gemini follows proper semantic versioning:

1. **Keep exact version pinning** - `"0.1.22"` without caret
2. **Monitor debug logs** - Track which methods are actually called
3. **Test thoroughly** before any version updates
4. **Use the Proxy pattern** - Provides safety net for unknown methods

### Related Issues

- Initial compatibility issue discovered: August 2025
- Breaking changes were introduced without major version bump
- No official migration guide provided by Google

### Contact

For questions about version compatibility, please open an issue on the repository.</doc><doc title="Gemini Cli Auth Options" desc="docs page."># @google/gemini-cli-core Authentication Options

Based on my analysis of the `@google/gemini-cli-core` package, here are the supported authentication options:

## Authentication Types

The core package supports three authentication methods, defined in the `AuthType` enum:

```typescript
export enum AuthType {
  LOGIN_WITH_GOOGLE = 'oauth-personal',
  USE_GEMINI = 'gemini-api-key',
  USE_VERTEX_AI = 'vertex-ai'
}
```

## 1. OAuth with Google Personal Account (`oauth-personal`)

- **Auth Type**: `AuthType.LOGIN_WITH_GOOGLE`
- **How it works**: Uses OAuth2 flow with Google authentication
- **Client ID**: `681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com`
- **Scopes**: 
  - `https://www.googleapis.com/auth/cloud-platform`
  - `https://www.googleapis.com/auth/userinfo.email`
  - `https://www.googleapis.com/auth/userinfo.profile`
- **Credentials cached at**: `~/.gemini/oauth_creds.json`
- **No API key required** - uses OAuth tokens instead

## 2. API Key Authentication

This provider supports both AI SDK standard and Gemini-specific auth types:

### AI SDK Standard (`api-key`) - Recommended
- **Auth Type**: `'api-key'` (AI SDK compliant)
- **Environment Variable**: `GEMINI_API_KEY`
- **How it works**: Direct API key authentication with Gemini service
- **Used with**: `GoogleGenAI` client from `@google/genai` package
- **Maps to**: `AuthType.USE_GEMINI`

### Gemini-Specific (`gemini-api-key`) - Alternative
- **Auth Type**: `'gemini-api-key'` (Gemini-specific)
- **Environment Variable**: `GEMINI_API_KEY`
- **How it works**: Same as above, alternative naming
- **Maps to**: `AuthType.USE_GEMINI`

## 3. Vertex AI (`vertex-ai`)

- **Auth Type**: `AuthType.USE_VERTEX_AI`
- **Environment Variables Required**:
  - `GOOGLE_API_KEY` - The API key for authentication
  - `GOOGLE_CLOUD_PROJECT` - The GCP project ID
  - `GOOGLE_CLOUD_LOCATION` - The GCP location/region
- **How it works**: Uses Vertex AI endpoint with API key authentication
- **Used with**: `GoogleGenAI` client with `vertexai: true` flag

## Client Initialization

The `GeminiClient` is initialized with a `Config` object that includes authentication configuration:

```typescript
// Create content generator config with auth type
const contentConfig = await createContentGeneratorConfig(
  model,
  authType,
  config
);

// Initialize the client
const geminiClient = new GeminiClient(config);
await geminiClient.initialize(contentConfig);
```

## Content Generator Configuration

The `ContentGeneratorConfig` interface includes:

```typescript
export type ContentGeneratorConfig = {
  model: string;
  apiKey?: string;
  vertexai?: boolean;
  authType?: AuthType | undefined;
};
```

## Additional Configuration

- **Proxy Support**: The client supports HTTP proxy configuration via the `proxy` parameter
- **Model Selection**: The model can be specified during initialization and changed at runtime
- **Flash Fallback**: OAuth users can fallback to Flash model when hitting rate limits

## Usage Example

```typescript
import { Config, GeminiClient, AuthType } from '@google/gemini-cli-core';

// Example with Gemini API Key
const config = new Config({
  sessionId: 'unique-session-id',
  targetDir: '/path/to/project',
  cwd: process.cwd(),
  model: 'gemini-2.0-flash-exp',
  debugMode: false
});

// Initialize with specific auth type
await config.refreshAuth(AuthType.USE_GEMINI);

// Get the client
const client = config.getGeminiClient();
```

## Authentication Setup

### For OAuth Authentication
```bash
# Initial setup - run and follow interactive prompts
gemini

# Or change auth method inside CLI
/auth
```

### For API Key Authentication
```bash
# Get your API key from Google AI Studio
export GEMINI_API_KEY="your-api-key-here"

# Or set in .gemini/.env file
mkdir -p .gemini
echo 'GEMINI_API_KEY="your-api-key"' >> .gemini/.env
```

### For Vertex AI Authentication
```bash
export GOOGLE_CLOUD_PROJECT="your-project-id"
export GOOGLE_CLOUD_LOCATION="us-central1"
export GOOGLE_GENAI_USE_VERTEXAI=true
export GEMINI_API_KEY="your-api-key"
```

## Key Points

1. **OAuth authentication** provides a seamless experience without requiring API keys
2. **API key authentication** supports both AI SDK standard (`'api-key'`) and Gemini-specific (`'gemini-api-key'`) auth types
3. **Credentials are cached** for OAuth to avoid repeated authentication
4. **The authentication type must be specified** when initializing the client
5. **Environment variables are checked** automatically based on the auth type
6. **Model selection is handled** differently for different auth types (with fallback logic for API keys)
7. **No "gemini auth login" command exists** - use `gemini` for interactive setup or `/auth` inside CLI</doc><doc title="Language Model V2 Implementation" desc="docs page."># LanguageModelV2 Implementation Summary for AI SDK v5

## Overview

The `doGenerate` and `doStream` methods are the core generation methods that all Language Model V2 providers must implement for AI SDK v5 compatibility. These methods handle standardized prompts and options, call the underlying model API, and return standardized results.

## Key Interfaces and Types

### 1. LanguageModelV2 Interface

The main interface that providers must implement for v5:

```typescript
export type LanguageModelV2 = {
  readonly specificationVersion: 'v2';
  readonly provider: string;
  readonly modelId: string;
  readonly defaultObjectGenerationMode: 'json' | 'tool' | undefined;
  readonly supportsImageUrls?: boolean;
  readonly supportsStructuredOutputs?: boolean;
  
  doGenerate(options: LanguageModelV1CallOptions): PromiseLike<{
    text?: string;
    reasoning?: string | Array<...>;
    files?: Array<{ data: string | Uint8Array; mimeType: string }>;
    toolCalls?: Array<LanguageModelV1FunctionToolCall>;
    finishReason: LanguageModelV1FinishReason;
    usage: { promptTokens: number; completionTokens: number };
    rawCall: { rawPrompt: unknown; rawSettings: Record<string, unknown> };
    rawResponse?: { headers?: Record<string, string>; body?: unknown };
    request?: { body?: string };
    response?: { id?: string; timestamp?: Date; modelId?: string };
    warnings?: LanguageModelV1CallWarning[];
    providerMetadata?: LanguageModelV1ProviderMetadata;
    sources?: LanguageModelV1Source[];
    logprobs?: LanguageModelV1LogProbs;
  }>;
  
  doStream(options: LanguageModelV1CallOptions): PromiseLike<{
    stream: ReadableStream<LanguageModelV1StreamPart>;
    // ... other properties
  }>;
};
```

### 2. LanguageModelV2CallOptions

The options passed to doGenerate and doStream in v5:

```typescript
export type LanguageModelV1CallOptions = LanguageModelV1CallSettings & {
  inputFormat: 'messages' | 'prompt';
  mode:
    | {
        type: 'regular';
        tools?: Array<LanguageModelV1FunctionTool | LanguageModelV1ProviderDefinedTool>;
        toolChoice?: LanguageModelV1ToolChoice;
      }
    | {
        type: 'object-json';
        schema?: JSONSchema7;
        name?: string;
        description?: string;
      }
    | {
        type: 'object-tool';
        tool: LanguageModelV1FunctionTool;
      };
  prompt: LanguageModelV1Prompt;
  providerMetadata?: LanguageModelV1ProviderMetadata;
};
```

### 3. LanguageModelV1CallSettings

Common generation settings:

```typescript
export type LanguageModelV1CallSettings = {
  maxTokens?: number;
  temperature?: number;
  stopSequences?: string[];
  topP?: number;
  topK?: number;
  presencePenalty?: number;
  frequencyPenalty?: number;
  responseFormat?: 
    | { type: 'text' }
    | { 
        type: 'json'; 
        schema?: JSONSchema7;
        name?: string;
        description?: string;
      };
  seed?: number;
  abortSignal?: AbortSignal;
  headers?: Record<string, string | undefined>;
};
```

### 4. LanguageModelV1Prompt

The standardized prompt format:

```typescript
export type LanguageModelV1Prompt = Array<LanguageModelV1Message>;

export type LanguageModelV1Message = 
  | {
      role: 'system';
      content: string;
    }
  | {
      role: 'user';
      content: Array<
        | LanguageModelV1TextPart
        | LanguageModelV1ImagePart
        | LanguageModelV1FilePart
      >;
    }
  | {
      role: 'assistant';
      content: Array<
        | LanguageModelV1TextPart
        | LanguageModelV1FilePart
        | LanguageModelV1ReasoningPart
        | LanguageModelV1RedactedReasoningPart
        | LanguageModelV1ToolCallPart
      >;
    }
  | {
      role: 'tool';
      content: Array<LanguageModelV1ToolResultPart>;
    };
```

### 5. Content Part Types

#### Text Part
```typescript
interface LanguageModelV1TextPart {
  type: 'text';
  text: string;
  providerMetadata?: LanguageModelV1ProviderMetadata;
}
```

#### Image Part
```typescript
interface LanguageModelV1ImagePart {
  type: 'image';
  image: Uint8Array | URL;
  mimeType?: string;
  providerMetadata?: LanguageModelV1ProviderMetadata;
}
```

#### Tool Call Part
```typescript
interface LanguageModelV1ToolCallPart {
  type: 'tool-call';
  toolCallId: string;
  toolName: string;
  args: unknown;
  providerMetadata?: LanguageModelV1ProviderMetadata;
}
```

#### Tool Result Part
```typescript
interface LanguageModelV1ToolResultPart {
  type: 'tool-result';
  toolCallId: string;
  toolName: string;
  result: unknown;
  isError?: boolean;
  content?: Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType?: string }>;
  providerMetadata?: LanguageModelV1ProviderMetadata;
}
```

### 6. Tool-Related Types

#### Function Tool Definition
```typescript
export type LanguageModelV1FunctionTool = {
  type: 'function';
  name: string;
  description?: string;
  parameters: JSONSchema7;
};
```

#### Tool Call Result
```typescript
export type LanguageModelV1FunctionToolCall = {
  toolCallType: 'function';
  toolCallId: string;
  toolName: string;
  args: string; // Stringified JSON
};
```

#### Tool Choice
```typescript
export type LanguageModelV1ToolChoice =
  | { type: 'auto' }
  | { type: 'none' }
  | { type: 'required' }
  | { type: 'tool'; toolName: string };
```

### 7. Result Types

#### Finish Reason
```typescript
export type LanguageModelV1FinishReason =
  | 'stop'           // model generated stop sequence
  | 'length'         // model generated maximum number of tokens
  | 'content-filter' // content filter violation stopped the model
  | 'tool-calls'     // model triggered tool calls
  | 'error'          // model stopped because of an error
  | 'other'          // model stopped for other reasons
  | 'unknown';       // the model has not transmitted a finish reason
```

#### Call Warning
```typescript
export type LanguageModelV1CallWarning = 
  | {
      type: 'unsupported-setting';
      setting: 'temperature' | 'maxTokens' | 'topP' | 'topK' | 'presencePenalty' | 'frequencyPenalty' | 'stopSequences' | 'seed';
      details?: string;
    }
  | {
      type: 'other';
      message: string;
    };
```

## Implementation Pattern

Based on the Claude Code provider example, here's the typical implementation pattern:

1. **Parse and validate options**
   - Extract settings from `LanguageModelV1CallOptions`
   - Validate model parameters
   - Generate warnings for unsupported settings

2. **Convert prompt to provider format**
   - Transform `LanguageModelV1Prompt` to provider-specific format
   - Handle different message roles and content types
   - Process multimodal content (images, files)

3. **Call the underlying API**
   - Use provider SDK/API with converted prompt
   - Handle abort signals
   - Manage authentication and errors

4. **Process the response**
   - Extract text, tool calls, and other content
   - Calculate token usage
   - Determine finish reason
   - For object-json mode, output is already schema-constrained via native `responseJsonSchema`

5. **Return standardized result**
   - Include all required fields (text, usage, finishReason, rawCall)
   - Add optional fields as available (toolCalls, warnings, providerMetadata)
   - Provide debugging information (rawResponse, request)

## Key Considerations

1. **Error Handling**: Use `@ai-sdk/provider` error types like `APICallError`, `NoSuchModelError`, `LoadAPIKeyError`

2. **Abort Signal**: Properly handle `options.abortSignal` for cancellation

3. **Mode Handling**:
   - `regular`: Standard text generation with optional tools
   - `object-json`: JSON generation mode (uses native `responseJsonSchema` for schema-constrained output)
   - `object-tool`: Tool-based object generation

4. **Warnings**: Generate warnings for unsupported parameters or validation issues

5. **Provider Metadata**: Pass through provider-specific data that doesn't fit standard fields

6. **Raw Data**: Include raw prompt/settings in `rawCall` for debugging and observability

This summary provides the essential types and patterns needed to implement a compliant `doGenerate` method for the Vercel AI SDK Language Model V1 interface.</doc><doc title="Project Structure" desc="docs page."># Project Structure

This document provides an overview of the ai-sdk-provider-gemini-cli codebase organization.

```
ai-sdk-provider-gemini-cli/
├── src/                                      # Source code
│   ├── index.ts                              # Main exports
│   ├── gemini-provider.ts                    # Provider factory function
│   ├── gemini-language-model.ts              # Core LanguageModelV2 implementation (v5)
│   ├── client.ts                             # Gemini CLI Core client initialization
│   ├── message-mapper.ts                     # Maps AI SDK messages to Gemini format
│   ├── tool-mapper.ts                        # Maps AI SDK tools to Gemini format
│   ├── error.ts                              # Error handling and mapping
│   ├── validation.ts                         # Input validation utilities
│   ├── types.ts                              # TypeScript type definitions
│   └── __tests__/                            # Unit tests
│       ├── client.test.ts                    # Client initialization tests
│       ├── error.test.ts                     # Error handling tests
│       ├── gemini-language-model.test.ts     # Language model tests
│       ├── gemini-provider.test.ts           # Provider creation tests
│       ├── index.test.ts                     # Main exports tests
│       ├── message-mapper.test.ts            # Message mapping tests
│       ├── tool-mapper.test.ts               # Tool mapping tests
│       └── validation.test.ts                # Validation logic tests
│
├── examples/                                 # Usage examples
│   ├── README.md                             # Examples documentation
│   ├── check-auth.mjs                        # Authentication verification
│   ├── basic-usage.mjs                       # Simple text generation
│   ├── streaming.mjs                         # Streaming responses
│   ├── conversation-history.mjs              # Multi-turn conversations
│   ├── system-messages.mjs                   # System prompts
│   ├── custom-config.mjs                     # Provider configuration
│   ├── error-handling.mjs                    # Error handling patterns
│   ├── long-running-tasks.mjs                # Timeout management
│   ├── integration-test.mjs                  # Comprehensive testing
│   └── generate-object-*.mjs                 # Object generation examples
│       ├── basic.mjs                         # Basic object generation
│       ├── nested.mjs                        # Nested structures
│       ├── constraints.mjs                   # Validation constraints
│       └── advanced.mjs                      # Complex real-world examples
│
├── docs/                                     # Technical documentation
│   ├── README.md                             # Documentation index
│   ├── project-structure.md                  # This file
│   ├── gemini-cli-auth-options.md            # Authentication details
│   ├── language-model-v2-implementation.md   # AI SDK v5 interface
│   ├── tool-schema-mapping.md                # Tool schema conversion
│   ├── zod-to-gemini-mapping.md              # Zod to Gemini mapping
│   └── ai-sdk-v5/                            # v5 specific docs
│       ├── BREAKING_CHANGES.md               # Migration guide from v4
│       ├── DEVELOPMENT_STATUS.md             # Current development status
│       ├── GUIDE.md                          # Comprehensive usage guide
│       └── TROUBLESHOOTING.md                # Common issues and solutions
│
├── dist/                                     # Build output (generated)
│   ├── index.js                              # CommonJS bundle
│   ├── index.mjs                             # ES Module bundle
│   ├── index.d.ts                            # TypeScript declarations
│   └── *.map                                 # Source maps
│
├── Configuration Files
│   ├── package.json                          # Project metadata and scripts
│   ├── tsconfig.json                         # TypeScript configuration
│   ├── tsconfig.build.json                   # Build-specific TS config
│   ├── tsup.config.ts                        # Build tool configuration
│   ├── vitest.config.ts                      # Test runner configuration
│   ├── eslint.config.js                      # ESLint configuration (flat config)
│   ├── .gitignore                            # Git ignore patterns
│   ├── .npmignore                            # NPM publish ignore patterns
│   └── .prettierrc                           # Code formatter configuration
│
└── Root Files
    ├── README.md                             # Main project documentation
    ├── CONTRIBUTING.md                       # Contributing guidelines
    └── LICENSE                               # MIT license

```

## Key Components

### Core Implementation (`src/`)

- **Provider Entry Points**
  - `index.ts` - Exports all public APIs
  - `gemini-provider.ts` - Factory function for creating providers

- **Language Model**
  - `gemini-language-model.ts` - Implements Vercel AI SDK's LanguageModelV2 interface for v5
  - Handles both streaming and non-streaming generation
  - Manages authentication and client initialization
  - Supports abort signals (with limitations)

- **Message & Tool Processing**
  - `message-mapper.ts` - Converts AI SDK message format to Gemini format
  - `tool-mapper.ts` - Converts function tools from Zod/JSON Schema to Gemini

- **Utilities**
  - `client.ts` - Initializes Gemini CLI Core with proper auth
  - `error.ts` - Maps Gemini errors to AI SDK error types
  - `validation.ts` - Validates model IDs and configurations
  - `logger.ts` - Logging utilities with verbose mode support

### Examples (`examples/`)

Organized by complexity and use case:
- **Getting Started**: Authentication, basic usage, streaming
- **Advanced Features**: Object generation, system messages, error handling
- **Testing**: Integration tests covering all features

### Documentation (`docs/`)

Technical documentation covering:
- Authentication options and setup
- Vercel AI SDK interface implementation
- Schema mapping and conversion details
- This project structure guide

## Development Workflow

1. **Source Code**: All TypeScript source in `src/`
2. **Build Output**: Generated in `dist/` via `npm run build`
3. **Examples**: Runnable examples in `examples/`
4. **Testing**: Run examples as integration tests

## Key Design Decisions

- **Minimal Dependencies**: Only essential packages included
- **Direct Integration**: Uses Gemini CLI Core directly without abstraction layers
- **Type Safety**: Full TypeScript support with comprehensive types
- **AI SDK Compatibility**: Implements standard LanguageModelV2 interface for v5
- **OAuth First**: Designed for OAuth authentication via Gemini CLI

## Test Coverage

The project includes comprehensive unit tests covering:
- Provider creation and configuration
- Message and tool mapping
- Error handling and validation
- Native JSON schema structured output
- Client initialization
- All core functionality

Current test coverage: ~98%</doc><doc title="Tool Schema Mapping" desc="docs page."># Tool Schema Mapping: Vercel AI SDK to Gemini

## Overview

This document outlines the mapping between Vercel AI SDK's Zod-based tool schemas and Google Gemini's FunctionDeclaration format.

**Note**: This mapping applies to both AI SDK v4 and v5. The tool schema format remains consistent across versions.

## Vercel AI SDK Tool Structure

```typescript
// Note: In v5, this is LanguageModelV2FunctionTool
// but the structure remains the same
interface LanguageModelV1FunctionTool {
  type: 'function';
  name: string;
  description?: string;
  parameters: JSONSchema7;
}
```

The AI SDK uses `zodSchema()` utility to convert Zod schemas to JSON Schema 7 format.

## Gemini FunctionDeclaration Format

```typescript
interface FunctionDeclaration {
  name: string;
  description?: string;
  // Option 1: Native Gemini Schema format
  parameters?: Schema;
  // Option 2: Standard JSON Schema (alternative)
  parametersJsonSchema?: object;
}

interface Schema {
  type: 'STRING' | 'NUMBER' | 'INTEGER' | 'BOOLEAN' | 'ARRAY' | 'OBJECT';
  description?: string;
  nullable?: boolean;
  enum?: string[];
  items?: Schema;  // For arrays
  properties?: { [key: string]: Schema };  // For objects
  required?: string[];  // For objects
  // Constraints (note: some use string type)
  minLength?: string;
  maxLength?: string;
  pattern?: string;
  minimum?: number;
  maximum?: number;
  minItems?: string;
  maxItems?: string;
}
```

## Type Mapping Table

### Basic Types

| JSON Schema Type | Gemini Schema Type | Notes |
|-----------------|-------------------|-------|
| `string` | `STRING` | |
| `number` | `NUMBER` | |
| `integer` | `INTEGER` | |
| `boolean` | `BOOLEAN` | |
| `array` | `ARRAY` | Requires `items` |
| `object` | `OBJECT` | Requires `properties` |

### Zod to JSON Schema to Gemini

| Zod Type | JSON Schema | Gemini Schema |
|----------|-------------|---------------|
| `z.string()` | `{type: 'string'}` | `{type: 'STRING'}` |
| `z.number()` | `{type: 'number'}` | `{type: 'NUMBER'}` |
| `z.boolean()` | `{type: 'boolean'}` | `{type: 'BOOLEAN'}` |
| `z.array(T)` | `{type: 'array', items: T}` | `{type: 'ARRAY', items: T}` |
| `z.object({...})` | `{type: 'object', properties: {...}}` | `{type: 'OBJECT', properties: {...}}` |
| `z.enum([...])` | `{enum: [...]}` | `{enum: [...]}` |
| `z.optional(T)` | `T` (not in required) | `T` (not in required) |
| `z.nullable(T)` | `{type: [T, 'null']}` | `{...T, nullable: true}` |

## Special Conversions

### Nullable Types
JSON Schema: `{type: ['string', 'null']}`
Gemini: `{type: 'STRING', nullable: true}`

### Const Values
JSON Schema: `{const: 'value'}`
Gemini: `{enum: ['value']}`

### Numeric Constraints
Note: Some Gemini constraints use string type:
- `minLength`, `maxLength`: string representation of number
- `minItems`, `maxItems`: string representation of number
- `minimum`, `maximum`: number type

### Union Types (anyOf)
Both formats support `anyOf` for union types.

## Implementation Approach

### Current Implementation: Convert to Native Gemini Schema
```typescript
function mapToolsToGeminiFormat(tools: LanguageModelV1FunctionTool[]): Tool[] {
  const functionDeclarations: FunctionDeclaration[] = [];

  for (const tool of tools) {
    functionDeclarations.push({
      name: tool.name,
      description: tool.description,
      parameters: convertToolParameters(tool.parameters),
    });
  }

  return [{ functionDeclarations }];
}
```

The implementation uses the native Gemini Schema format (`parameters` field) rather than `parametersJsonSchema`. This ensures maximum compatibility with the Gemini CLI Core library.

## Unsupported Features

1. **References ($ref)**: Gemini doesn't support JSON Schema references
2. **allOf**: Not directly supported, needs manual merging
3. **Tuple arrays**: Not supported in Gemini
4. **Complex validation**: Some JSON Schema validation rules have no Gemini equivalent

## Testing Considerations

1. Test with simple types (string, number, boolean)
2. Test with nested objects and arrays
3. Test with optional and nullable fields
4. Test with enums and const values
5. Test with complex real-world schemas
6. Verify constraint conversion (especially string vs number types)</doc><doc title="Zod To Gemini Mapping" desc="docs page."># Zod to Gemini Function Declaration Mapping

## Overview

This document provides a comprehensive mapping between Vercel AI SDK's use of Zod schemas for tool definitions and Google Gemini's FunctionDeclaration format requirements.

**Compatibility Note**: This mapping applies to both AI SDK v4 and v5. While v5 uses `LanguageModelV2` interfaces, the Zod schema conversion process remains the same.

## Core Type Definitions

### Vercel AI SDK Tool Structure

```typescript
// From @ai-sdk/provider
// Note: In v5 this is LanguageModelV2FunctionTool
interface LanguageModelV1FunctionTool {
  type: 'function';
  name: string;
  description?: string;
  parameters: JSONSchema7;  // JSON Schema format
}

// From @ai-sdk/ai-core
interface Tool<PARAMETERS extends ToolParameters = any, RESULT = any> {
  parameters: PARAMETERS;  // Can be z.ZodTypeAny or Schema<any>
  description?: string;
  execute?: (args: inferParameters<PARAMETERS>, options: ToolExecutionOptions) => PromiseLike<RESULT>;
}
```

### Google Gemini FunctionDeclaration

```typescript
// From @google/genai
interface FunctionDeclaration {
  name: string;
  description?: string;
  parameters?: Schema;  // Gemini's custom Schema format
  parametersJsonSchema?: unknown;  // Alternative: standard JSON Schema
  behavior?: Behavior;
}

interface Schema {
  type?: Type;  // 'TYPE_UNSPECIFIED' | 'STRING' | 'NUMBER' | 'INTEGER' | 'BOOLEAN' | 'ARRAY' | 'OBJECT'
  format?: string;
  description?: string;
  nullable?: boolean;
  enum?: string[];
  items?: Schema;  // For arrays
  properties?: { [key: string]: Schema };  // For objects
  required?: string[];  // For objects
  anyOf?: Schema[];
  default?: unknown;
  example?: unknown;
  
  // String constraints
  maxLength?: string;
  minLength?: string;
  pattern?: string;
  
  // Number constraints
  minimum?: number;
  maximum?: number;
  exclusiveMinimum?: boolean;
  exclusiveMaximum?: boolean;
  
  // Array constraints
  minItems?: string;
  maxItems?: string;
  uniqueItems?: boolean;
  
  // Object constraints
  minProperties?: string;
  maxProperties?: string;
  additionalProperties?: boolean;
}
```

## Conversion Strategy

### 1. Tool Definition Conversion

```typescript
function convertVercelToolToGemini(tool: LanguageModelV1FunctionTool): FunctionDeclaration {
  return {
    name: tool.name,
    description: tool.description,
    parameters: convertJSONSchemaToGeminiSchema(tool.parameters)
  };
}
```

### 2. Zod to JSON Schema to Gemini Schema

The conversion happens in two steps:
1. **Zod → JSON Schema**: Using `zod-to-json-schema` library (already done by Vercel AI SDK)
2. **JSON Schema → Gemini Schema**: Custom conversion logic

### 3. Type Mapping Table

| Zod Type | JSON Schema Type | Gemini Schema Type | Notes |
|----------|------------------|-------------------|-------|
| `z.string()` | `{ type: 'string' }` | `{ type: 'STRING' }` | |
| `z.number()` | `{ type: 'number' }` | `{ type: 'NUMBER' }` | |
| `z.boolean()` | `{ type: 'boolean' }` | `{ type: 'BOOLEAN' }` | |
| `z.literal()` | `{ const: value }` | `{ enum: [value] }` | Single enum value |
| `z.enum()` | `{ enum: [...] }` | `{ type: 'STRING', enum: [...] }` | |
| `z.array()` | `{ type: 'array', items: {...} }` | `{ type: 'ARRAY', items: {...} }` | |
| `z.object()` | `{ type: 'object', properties: {...} }` | `{ type: 'OBJECT', properties: {...} }` | |
| `z.union()` | `{ anyOf: [...] }` | `{ anyOf: [...] }` | |
| `z.optional()` | `{ type: [..., 'null'] }` | `{ nullable: true }` | |
| `z.nullable()` | `{ type: [..., 'null'] }` | `{ nullable: true }` | |
| `z.record()` | `{ type: 'object', additionalProperties: {...} }` | `{ type: 'OBJECT', additionalProperties: true }` | |
| `z.tuple()` | `{ type: 'array', items: [...] }` | Not directly supported | Convert to array with items schema |
| `z.intersection()` | `{ allOf: [...] }` | Not directly supported | Merge properties |
| `z.lazy()` | `{ $ref: '...' }` | Not supported | Requires schema flattening |

### 4. Format Mapping

| Zod/JSON Schema Format | Gemini Format | Notes |
|----------------------|---------------|-------|
| `email` | `email` | |
| `url` | `uri` | |
| `uuid` | `uuid` | |
| `date-time` | `date-time` | |
| `date` | `date` | |
| `time` | `time` | |
| `ipv4` | `ipv4` | |
| `ipv6` | `ipv6` | |
| `hostname` | Not supported | Use string |
| `json-pointer` | Not supported | Use string |
| `regex` | Use `pattern` property | |

### 5. Constraint Mapping

#### String Constraints
```typescript
// Zod
z.string().min(5).max(100).regex(/^[A-Z]/)

// JSON Schema
{
  type: 'string',
  minLength: 5,
  maxLength: 100,
  pattern: '^[A-Z]'
}

// Gemini Schema
{
  type: 'STRING',
  minLength: '5',  // Note: string type
  maxLength: '100', // Note: string type
  pattern: '^[A-Z]'
}
```

#### Number Constraints
```typescript
// Zod
z.number().min(0).max(100).int()

// JSON Schema
{
  type: 'integer',
  minimum: 0,
  maximum: 100
}

// Gemini Schema
{
  type: 'INTEGER',
  minimum: 0,
  maximum: 100
}
```

#### Array Constraints
```typescript
// Zod
z.array(z.string()).min(1).max(10)

// JSON Schema
{
  type: 'array',
  items: { type: 'string' },
  minItems: 1,
  maxItems: 10
}

// Gemini Schema
{
  type: 'ARRAY',
  items: { type: 'STRING' },
  minItems: '1',  // Note: string type
  maxItems: '10'  // Note: string type
}
```

## Implementation Guidelines

### 1. Conversion Function Structure

```typescript
function convertJSONSchemaToGeminiSchema(jsonSchema: JSONSchema7): Schema {
  // Handle boolean schemas
  if (typeof jsonSchema === 'boolean') {
    return { type: 'BOOLEAN' };
  }

  const geminiSchema: Schema = {};

  // Convert type
  if (jsonSchema.type) {
    geminiSchema.type = mapJSONSchemaTypeToGemini(jsonSchema.type);
  }

  // Convert constraints based on type
  if (geminiSchema.type === 'STRING') {
    if (jsonSchema.minLength !== undefined) {
      geminiSchema.minLength = String(jsonSchema.minLength);
    }
    if (jsonSchema.maxLength !== undefined) {
      geminiSchema.maxLength = String(jsonSchema.maxLength);
    }
    if (jsonSchema.pattern) {
      geminiSchema.pattern = jsonSchema.pattern;
    }
  }

  // Handle objects
  if (geminiSchema.type === 'OBJECT' && jsonSchema.properties) {
    geminiSchema.properties = {};
    for (const [key, value] of Object.entries(jsonSchema.properties)) {
      geminiSchema.properties[key] = convertJSONSchemaToGeminiSchema(value);
    }
    if (jsonSchema.required) {
      geminiSchema.required = jsonSchema.required;
    }
  }

  // Handle arrays
  if (geminiSchema.type === 'ARRAY' && jsonSchema.items) {
    geminiSchema.items = convertJSONSchemaToGeminiSchema(jsonSchema.items);
  }

  // Copy common properties
  if (jsonSchema.description) {
    geminiSchema.description = jsonSchema.description;
  }
  if (jsonSchema.enum) {
    geminiSchema.enum = jsonSchema.enum.map(String);
  }
  if (jsonSchema.default !== undefined) {
    geminiSchema.default = jsonSchema.default;
  }

  return geminiSchema;
}
```

### 2. Type Mapping Helper

```typescript
function mapJSONSchemaTypeToGemini(type: JSONSchema7TypeName | JSONSchema7TypeName[]): string {
  if (Array.isArray(type)) {
    // Handle nullable types
    const nonNullTypes = type.filter(t => t !== 'null');
    if (nonNullTypes.length === 1) {
      return mapSingleType(nonNullTypes[0]);
    }
    // Multiple non-null types not directly supported
    return 'TYPE_UNSPECIFIED';
  }
  return mapSingleType(type);
}

function mapSingleType(type: JSONSchema7TypeName): string {
  switch (type) {
    case 'string': return 'STRING';
    case 'number': return 'NUMBER';
    case 'integer': return 'INTEGER';
    case 'boolean': return 'BOOLEAN';
    case 'array': return 'ARRAY';
    case 'object': return 'OBJECT';
    case 'null': return 'TYPE_UNSPECIFIED';
    default: return 'TYPE_UNSPECIFIED';
  }
}
```

### 3. Special Cases Handling

#### Nullable Types
```typescript
// JSON Schema: { type: ['string', 'null'] }
// Gemini: { type: 'STRING', nullable: true }
```

#### Union Types (anyOf)
```typescript
// JSON Schema: { anyOf: [{ type: 'string' }, { type: 'number' }] }
// Gemini: { anyOf: [{ type: 'STRING' }, { type: 'NUMBER' }] }
```

#### Empty Objects
```typescript
// JSON Schema: { type: 'object', properties: {} }
// Gemini: omit parameters entirely or use parametersJsonSchema
```

## Alternative Approach: Using parametersJsonSchema

Gemini also supports standard JSON Schema through the `parametersJsonSchema` field:

```typescript
function convertVercelToolToGeminiWithJsonSchema(tool: LanguageModelV1FunctionTool): FunctionDeclaration {
  return {
    name: tool.name,
    description: tool.description,
    parametersJsonSchema: tool.parameters  // Use JSON Schema directly
  };
}
```

This approach may be simpler but requires verifying that Gemini CLI Core supports this field.

## Testing Considerations

1. **Type Coverage**: Test all Zod primitive types and their combinations
2. **Constraint Validation**: Ensure numeric constraints are properly converted to strings where required
3. **Nested Structures**: Test deeply nested objects and arrays
4. **Edge Cases**: Empty objects, null values, undefined properties
5. **Schema References**: Handle or reject recursive schemas appropriately

## Recommendations

1. **Primary Approach**: Convert to Gemini's native Schema format for maximum compatibility
2. **Fallback**: Use `parametersJsonSchema` if supported by Gemini CLI Core
3. **Validation**: Implement runtime validation to ensure converted schemas are valid
4. **Documentation**: Document any limitations or unsupported features
5. **Error Handling**: Provide clear error messages for unsupported schema patterns</doc><doc title="Breaking Changes" desc="docs page."># Breaking Changes: AI SDK v5

This document outlines the breaking changes when migrating from AI SDK v4 to v5 for the Gemini CLI provider.

## Overview

The Vercel AI SDK v5 introduces significant architectural changes that affect how providers are implemented and used. This provider has been updated to be fully compatible with v5.

## Key Breaking Changes

### 1. Response Format Changes

**v4 Response:**
```typescript
const { text, usage } = await generateText({
  model: gemini('gemini-2.5-pro'),
  prompt: 'Hello',
});
```

**v5 Response:**
```typescript
const result = await generateText({
  model: gemini('gemini-2.5-pro'),
  prompt: 'Hello',
});

// Access properties differently:
console.log(result.text);           // The generated text
console.log(result.usage);          // Token usage info
console.log(result.content[0].text); // Alternative access
```

### 2. Parameter Name Changes

Several parameter names have been updated to align with v5 conventions:

| v4 Parameter | v5 Parameter | Notes |
|--------------|-------------------|-------|
| `maxTokens` | `maxOutputTokens` | Maximum tokens to generate |
| `stopWords` | `stopSequences` | Sequences that stop generation |

### 3. Streaming API Changes

**v4 Streaming:**
```typescript
const { textStream } = await streamText({
  model: gemini('gemini-2.5-pro'),
  prompt: 'Tell me a story',
});

for await (const chunk of textStream) {
  process.stdout.write(chunk);
}
```

**v5 Streaming:**
```typescript
const result = await streamText({
  model: gemini('gemini-2.5-pro'),
  prompt: 'Tell me a story',
});

// Now returns a promise with stream properties
for await (const chunk of result.textStream) {
  process.stdout.write(chunk);
}

// Can also access the full text after streaming
const fullText = await result.text;
```

### 4. Token Usage Property Names

Token usage reporting has been standardized:

**v4:**
```typescript
{
  promptTokens: 10,
  completionTokens: 50,
  totalTokens: 60
}
```

**v5:**
```typescript
{
  inputTokens: 10,
  outputTokens: 50,
  totalTokens: 60
}
```

### 5. Message Format Requirements

v5 enforces stricter message formats:

```typescript
// Messages must have proper role types
messages: [
  { role: 'user', content: 'Hello' },
  { role: 'assistant', content: 'Hi there!' },
  { role: 'user', content: 'How are you?' }
]
```

### 6. Provider Interface Changes

The provider now extends `ProviderV2` and implements `LanguageModelV2`:

```typescript
// Provider extends ProviderV2
class GeminiProvider extends ProviderV2 {
  // Returns LanguageModelV2 instances
}
```

### 7. Error Handling

Error handling has been improved with better error types and messages:

```typescript
try {
  const result = await generateText({
    model: gemini('gemini-2.5-pro'),
    prompt: 'Hello',
  });
} catch (error) {
  // Errors now have consistent structure
  if (error.name === 'AbortError') {
    // Handle cancellation
  }
}
```

### 8. Object Generation

The `generateObject` function now has stricter schema validation:

```typescript
// Schema validation errors now show as:
// "No object generated: could not parse the response"
// This actually means validation failed, not parsing
```

## Migration Guide

### Step 1: Update Dependencies

```bash
npm install ai-sdk-provider-gemini-cli@beta ai@beta
```

### Step 2: Update Import Statements

No changes needed - imports remain the same:

```typescript
import { generateText, streamText } from 'ai';
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';
```

### Step 3: Update Response Handling

Replace all instances of destructured responses:

```typescript
// Old
const { text, usage } = await generateText(...);

// New
const result = await generateText(...);
const text = result.text;
const usage = result.usage;
```

### Step 4: Update Parameter Names

Search and replace parameter names:
- `maxTokens` → `maxOutputTokens`
- `stopWords` → `stopSequences`

### Step 5: Update Token Usage Access

Update any code that accesses token usage:

```typescript
// Old
console.log(usage.promptTokens);
console.log(usage.completionTokens);

// New
console.log(usage.inputTokens);
console.log(usage.outputTokens);
```

### Step 6: Test Thoroughly

Run all tests and examples to ensure compatibility:

```bash
npm run build
npm run example:test
```

## Known Issues

1. **maxOutputTokens with gemini-2.5-pro**: Setting `maxOutputTokens` can cause empty responses with gemini-2.5-pro. Consider omitting this parameter or using gemini-2.5-flash.

2. **Abort Signal Limitation**: The underlying gemini-cli-core doesn't support request cancellation. Abort signals work from the SDK perspective but requests continue in the background.

3. **Schema Validation Messages**: When using `generateObject`, validation failures show misleading "could not parse" errors even though JSON parsing succeeded.

## Need Help?

- Check the [examples](../../examples/) directory for v5 usage patterns
- Review the [GUIDE.md](./GUIDE.md) for detailed usage instructions
- See [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) for common issues</doc><doc title="Development Status" desc="docs page."># Development Status for AI SDK v5

## Overview

This document tracks the development status of the Gemini CLI Provider for Vercel AI SDK v5 compatibility.

## Current Status: ✅ COMPLETE

The provider has been fully migrated to support AI SDK v5.

## Completed Features

### Core Functionality
- ✅ **Provider Interface**: Extends `ProviderV2` correctly
- ✅ **Language Model**: Implements `LanguageModelV2` interface
- ✅ **Text Generation**: Full `generateText` support with v5 response format
- ✅ **Streaming**: Complete `streamText` implementation with promise-based API
- ✅ **Object Generation**: `generateObject` with Zod schema validation
- ✅ **System Messages**: Proper system instruction support
- ✅ **Conversation History**: Multi-turn conversation support
- ✅ **Multimodal**: Base64 image support (URL images not supported by design)

### Authentication
- ✅ **OAuth Personal**: Default authentication via Gemini CLI
- ✅ **API Key**: Both `api-key` and `gemini-api-key` auth types
- ✅ **Credential Management**: Uses `~/.gemini/oauth_creds.json`

### Models
- ✅ **gemini-3-pro-preview**: Full support (Preview)
- ✅ **gemini-2.5-pro**: Full support (Previous generation)
- ✅ **gemini-2.5-flash**: Full support for faster responses

### Error Handling
- ✅ **Error Mapping**: Proper error types for v5
- ✅ **Abort Signals**: Correct AbortError handling (with limitations)
- ✅ **Validation Errors**: Clear error messages for schema failures

### Documentation
- ✅ **Breaking Changes Guide**: Complete migration guide from v4
- ✅ **Usage Guide**: Comprehensive v5 patterns and examples
- ✅ **Troubleshooting**: Common issues and solutions documented
- ✅ **API Documentation**: All interfaces documented

### Examples
- ✅ All 14 example files updated and tested with v5
- ✅ Examples use gemini-2.5-pro for consistency
- ✅ Clear documentation of patterns and best practices

## Known Limitations

### 1. Abort Signal Support
- **Status**: Partial
- **Issue**: The underlying `gemini-cli-core` doesn't support request cancellation
- **Impact**: Abort signals work from SDK perspective but HTTP requests continue in background
- **Workaround**: None - this is a limitation of the underlying library

### 2. maxOutputTokens with gemini-2.5-pro
- **Status**: Known Issue
- **Issue**: Setting `maxOutputTokens` can cause empty responses
- **Impact**: Users may get unexpected empty results
- **Workaround**: Omit the parameter or use gemini-2.5-flash

### 3. Image URL Support
- **Status**: Not Supported
- **Issue**: Only base64-encoded images are supported
- **Impact**: Users must convert images to base64
- **Workaround**: Read images as buffers and encode to base64

### 4. Unsupported Parameters
- **frequencyPenalty**: Not supported by Gemini
- **presencePenalty**: Not supported by Gemini
- **seed**: Not supported by Gemini
- **responseFormat**: Partially supported (JSON mode only)

## Testing Status

### Unit Tests
- ✅ All tests updated for v5 compatibility
- ✅ 98.85% test coverage achieved
- ✅ All tests passing

### Integration Tests
- ✅ All examples run successfully
- ✅ Authentication verified
- ✅ Model responses validated

### Manual Testing
- ✅ Basic text generation
- ✅ Streaming responses
- ✅ Object generation
- ✅ System messages
- ✅ Conversation history
- ✅ Error scenarios
- ✅ Timeout/abort handling

## Migration Checklist

- [x] Update dependencies to v5 versions
- [x] Implement ProviderV2 interface
- [x] Implement LanguageModelV2 interface
- [x] Update message format handling
- [x] Update streaming implementation
- [x] Update token usage property names
- [x] Update parameter names (maxTokens → maxOutputTokens)
- [x] Update error handling
- [x] Update all examples
- [x] Update all documentation
- [x] Run comprehensive tests

## Version Information

- **Provider Version**: 1.0.0-beta.x
- **AI SDK Version**: 5.0.0-beta.26+
- **AI SDK Provider**: 2.0.0-beta.1+
- **Node.js**: ≥20 required

## Future Considerations

1. **Request Cancellation**: If `gemini-cli-core` adds abort support, update provider
2. **New Models**: Add support for new Gemini models as they become available
3. **Additional Features**: Monitor AI SDK v5 for new features to support
4. **Performance**: Consider optimization opportunities for streaming

## Support

For issues or questions:
- Review [TROUBLESHOOTING.md](./TROUBLESHOOTING.md)
- Check [examples](../../examples/) for patterns
- File issues on GitHub repository</doc><doc title="Guide" desc="docs page."># Gemini CLI Provider for AI SDK v5 Guide

This guide covers how to use the Gemini CLI Provider with Vercel AI SDK v5.

## Table of Contents

- [Installation](#installation)
- [Authentication](#authentication)
- [Basic Usage](#basic-usage)
- [Streaming](#streaming)
- [Conversation History](#conversation-history)
- [System Messages](#system-messages)
- [Structured Output](#structured-output)
- [Error Handling](#error-handling)
- [Advanced Features](#advanced-features)
- [Best Practices](#best-practices)

## Installation

```bash
# Install the beta versions
npm install ai-sdk-provider-gemini-cli@beta ai@beta

# Install and set up Gemini CLI
npm install -g @google/gemini-cli
gemini  # Follow authentication setup
```

## Authentication

### OAuth Authentication (Recommended)

```typescript
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';

const gemini = createGeminiProvider({
  authType: 'oauth-personal',
});
```

### API Key Authentication

```typescript
const gemini = createGeminiProvider({
  authType: 'api-key',
  apiKey: process.env.GEMINI_API_KEY,
});
```

## Basic Usage

### Text Generation

```typescript
import { generateText } from 'ai';
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';

const gemini = createGeminiProvider({
  authType: 'oauth-personal',
});

async function generate() {
  const result = await generateText({
    model: gemini('gemini-2.5-pro'),
    prompt: 'Write a haiku about coding',
  });

  console.log(result.text);
  console.log(`Tokens used: ${result.usage?.totalTokens}`);
}
```

### Model Configuration

```typescript
const model = gemini('gemini-2.5-pro', {
  temperature: 0.7,        // Creativity (0-2)
  maxOutputTokens: 1000,   // Max tokens to generate
  topP: 0.95,             // Nucleus sampling
  topK: 40,               // Top-k sampling
});
```

## Streaming

### Basic Streaming

```typescript
import { streamText } from 'ai';

async function stream() {
  const result = await streamText({
    model: gemini('gemini-2.5-pro'),
    prompt: 'Tell me a story about a robot',
  });

  // Stream chunks as they arrive
  for await (const chunk of result.textStream) {
    process.stdout.write(chunk);
  }

  // Access full text after streaming
  const fullText = await result.text;
  console.log('\n\nFull text length:', fullText.length);
}
```

### Progress Tracking

```typescript
async function streamWithProgress() {
  const result = await streamText({
    model: gemini('gemini-2.5-pro'),
    prompt: 'Write a detailed article about AI',
  });

  let charCount = 0;
  const startTime = Date.now();

  for await (const chunk of result.textStream) {
    charCount += chunk.length;
    const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
    process.stdout.write(`\r📝 Generated: ${charCount} chars | Time: ${elapsed}s`);
  }

  console.log('\n✅ Complete!');
}
```

## Conversation History

### Multi-turn Conversations

```typescript
async function conversation() {
  const result = await generateText({
    model: gemini('gemini-2.5-pro'),
    messages: [
      { role: 'user', content: 'My name is Alice' },
      { role: 'assistant', content: 'Nice to meet you, Alice! How can I help you today?' },
      { role: 'user', content: 'What is my name?' },
    ],
  });

  console.log(result.text); // Should remember "Alice"
}
```

### Building Conversation Context

```typescript
const messages = [];

function addUserMessage(content: string) {
  messages.push({ role: 'user', content });
}

function addAssistantMessage(content: string) {
  messages.push({ role: 'assistant', content });
}

async function continueConversation(userInput: string) {
  addUserMessage(userInput);
  
  const result = await generateText({
    model: gemini('gemini-2.5-pro'),
    messages,
  });

  addAssistantMessage(result.text);
  return result.text;
}
```

## System Messages

### Setting Model Behavior

```typescript
async function withSystemMessage() {
  const result = await generateText({
    model: gemini('gemini-2.5-pro'),
    system: 'You are a helpful coding assistant. Always include code examples in your responses.',
    prompt: 'How do I read a file in Node.js?',
  });

  console.log(result.text); // Will include code examples
}
```

### Complex System Instructions

```typescript
const system = `You are an expert TypeScript developer.
- Always use modern ES6+ syntax
- Include type annotations
- Follow best practices
- Explain your code clearly`;

const result = await generateText({
  model: gemini('gemini-2.5-pro'),
  system,
  prompt: 'Create a generic cache class',
});
```

## Structured Output

### Basic Object Generation

```typescript
import { generateObject } from 'ai';
import { z } from 'zod';

async function generateProduct() {
  const result = await generateObject({
    model: gemini('gemini-2.5-pro'),
    schema: z.object({
      name: z.string().describe('Product name'),
      price: z.number().describe('Price in USD'),
      inStock: z.boolean().describe('Availability'),
    }),
    prompt: 'Generate a laptop product',
  });

  console.log(result.object);
  // { name: "UltraBook Pro", price: 1299.99, inStock: true }
}
```

### Nested Structures

```typescript
const CompanySchema = z.object({
  name: z.string(),
  founded: z.number(),
  employees: z.array(z.object({
    name: z.string(),
    role: z.string(),
    department: z.string(),
  })),
  metrics: z.object({
    revenue: z.number(),
    growth: z.number(),
  }),
});

const result = await generateObject({
  model: gemini('gemini-2.5-pro'),
  schema: CompanySchema,
  prompt: 'Generate a tech startup company profile',
});
```

### Handling Validation

```typescript
try {
  const result = await generateObject({
    model: gemini('gemini-2.5-pro'),
    schema: z.object({
      description: z.string().max(100), // Strict limit
    }),
    prompt: 'Describe quantum computing',
  });
} catch (error) {
  // Note: Error may say "could not parse" but usually means
  // validation failed (e.g., string too long)
  console.error('Validation failed:', error.message);
}
```

## Error Handling

### Basic Error Handling

```typescript
try {
  const result = await generateText({
    model: gemini('gemini-2.5-pro'),
    prompt: 'Hello',
  });
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Request was cancelled');
  } else if (error.message.includes('quota')) {
    console.log('Rate limit exceeded');
  } else {
    console.error('Unexpected error:', error);
  }
}
```

### Timeout Management

```typescript
async function withTimeout() {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 10000); // 10 seconds

  try {
    const result = await generateText({
      model: gemini('gemini-2.5-pro'),
      prompt: 'Write a detailed analysis',
      abortSignal: controller.signal,
    });
    
    clearTimeout(timeout);
    return result.text;
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Request timed out');
    }
    throw error;
  }
}
```

**Note**: Due to gemini-cli-core limitations, aborted requests continue in the background even though the SDK throws AbortError.

## Advanced Features

### Multimodal Input (Images)

```typescript
import { readFileSync } from 'fs';

async function analyzeImage() {
  const imageBuffer = readFileSync('diagram.png');
  const base64Image = imageBuffer.toString('base64');

  const result = await generateText({
    model: gemini('gemini-2.5-pro'),
    messages: [{
      role: 'user',
      content: [
        { type: 'text', text: 'What is shown in this image?' },
        { type: 'image', data: base64Image },
      ],
    }],
  });

  console.log(result.text);
}
```

### Token Usage Monitoring

```typescript
async function trackUsage() {
  const results = [];
  
  for (const prompt of prompts) {
    const result = await generateText({
      model: gemini('gemini-2.5-pro'),
      prompt,
    });
    
    results.push({
      prompt: prompt.substring(0, 50),
      inputTokens: result.usage?.inputTokens || 0,
      outputTokens: result.usage?.outputTokens || 0,
      totalTokens: result.usage?.totalTokens || 0,
    });
  }
  
  const totalTokens = results.reduce((sum, r) => sum + r.totalTokens, 0);
  console.log('Total tokens used:', totalTokens);
}
```

## Best Practices

### 1. Model Selection

- Use **gemini-3-pro-preview** for enhanced reasoning capabilities
- Use **gemini-2.5-pro** for production workloads requiring the previous generation stable model
- Use **gemini-2.5-flash** for simpler tasks where speed is important

### 2. Prompt Engineering

```typescript
// Be specific and clear
const goodPrompt = `Write a Python function that:
1. Takes a list of integers as input
2. Returns the sum of even numbers
3. Includes type hints
4. Has a docstring`;

// Avoid vague prompts
const badPrompt = 'Write a function';
```

### 3. Error Recovery

```typescript
async function generateWithRetry(prompt: string, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const result = await generateText({
        model: gemini('gemini-2.5-pro'),
        prompt,
      });
      return result;
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      
      // Wait before retry (exponential backoff)
      await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
    }
  }
}
```

### 4. Memory Management

For long conversations, consider truncating message history:

```typescript
function truncateMessages(messages: any[], maxMessages = 20) {
  if (messages.length <= maxMessages) return messages;
  
  // Keep system message (if any) and recent messages
  const systemMsg = messages.find(m => m.role === 'system');
  const recentMessages = messages.slice(-maxMessages);
  
  return systemMsg ? [systemMsg, ...recentMessages] : recentMessages;
}
```

### 5. Streaming Best Practices

```typescript
// Clean up resources on error
async function safeStream() {
  let result;
  
  try {
    result = await streamText({
      model: gemini('gemini-2.5-pro'),
      prompt: 'Tell me a story',
    });
    
    for await (const chunk of result.textStream) {
      process.stdout.write(chunk);
    }
  } catch (error) {
    console.error('Stream error:', error);
    // Ensure stream is properly closed
    if (result?.textStream) {
      result.textStream.return?.();
    }
  }
}
```

## Next Steps

- Explore the [examples](../../examples/) directory for more patterns
- Check [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) for common issues
- Review [BREAKING_CHANGES.md](./BREAKING_CHANGES.md) if migrating from v4</doc></docs><tutorials><doc title="README" desc="install &amp; quickstart."># Gemini CLI AI SDK Provider Examples

This directory contains curated examples demonstrating the key features of the Gemini CLI AI SDK Provider. Each example shows how to use Google's Gemini models through the Cloud Code endpoints with the Vercel AI SDK.

## Prerequisites

1. Authenticate with Google Cloud Code:
```bash
# Run the gemini CLI and follow interactive setup
gemini
```

2. Build the provider:
```bash
npm run build
```

3. Verify your setup:
```bash
node examples/check-auth.mjs
```

## Quick Start Examples

### 1. Basic Usage (`basic-usage.mjs`)
**Purpose**: The simplest example - generate text with Gemini and display metadata.
```bash
node examples/basic-usage.mjs
```
**Key concepts**: Text generation, token usage, OAuth authentication

### 2. Streaming (`streaming.mjs`)
**Purpose**: Demonstrate real-time streaming for responsive user experiences.
```bash
node examples/streaming.mjs
```
**Key concepts**: Stream processing, chunk handling, real-time output

### 3. Conversation History (`conversation-history.mjs`)
**Purpose**: Show how to maintain context across multiple messages.
```bash
node examples/conversation-history.mjs
```
**Key concepts**: Message history, context preservation, multi-turn conversations

## Logging Examples

The provider includes a flexible logging system that can be configured for different use cases. These examples demonstrate all logging modes:

### 4. Default Logging (`logging-default.mjs`)

**Purpose**: Understand the default logging behavior (non-verbose mode).

```bash
node examples/logging-default.mjs
```

**Key concepts**: Default behavior, warn/error only, clean output

**What you'll see**: Only warning and error messages appear. Debug and info logs are suppressed for clean output.

### 5. Verbose Logging (`logging-verbose.mjs`)

**Purpose**: Enable detailed logging for development and troubleshooting.

```bash
node examples/logging-verbose.mjs
```

**Key concepts**: Verbose mode, debug/info logs, execution tracing

**What you'll see**: All log levels (debug, info, warn, error) showing detailed provider activity.

### 6. Custom Logger (`logging-custom-logger.mjs`)

**Purpose**: Integrate with external logging systems (Winston, Pino, Datadog, etc.).

```bash
node examples/logging-custom-logger.mjs
```

**Key concepts**: Custom logger implementation, external integration, log formatting

**What you'll see**: Custom-formatted logs with timestamps and prefixes, demonstrating integration patterns.

### 7. Disabled Logging (`logging-disabled.mjs`)

**Purpose**: Completely silent operation with no logs.

```bash
node examples/logging-disabled.mjs
```

**Key concepts**: Silent mode, production deployments, zero output

**What you'll see**: No provider logs at all - completely silent operation.

## Advanced Configuration

### 8. Custom Config (`custom-config.mjs`)
**Purpose**: Demonstrate provider configuration options.
```bash
node examples/custom-config.mjs
```
**Key concepts**: API key auth, OAuth auth, model settings

### 9. System Messages (`system-messages.mjs`)
**Purpose**: Use system prompts to control model behavior.
```bash
node examples/system-messages.mjs
```
**Key concepts**: System instructions, persona control, response formatting

### 10. Long-Running Tasks (`long-running-tasks.mjs`)
**Purpose**: Handle complex tasks with proper timeout management.
```bash
node examples/long-running-tasks.mjs
```
**Key concepts**: AbortSignal, timeout handling, complex reasoning

## Object Generation (Structured Output)

### 11. Object Generation Basic (`generate-object-basic.mjs`)
**Purpose**: Learn structured output generation step-by-step.
```bash
node examples/generate-object-basic.mjs
```
**Key concepts**: Zod schemas, JSON generation, validation

### 12. Nested Structures (`generate-object-nested.mjs`)
**Purpose**: Generate complex hierarchical data structures.
```bash
node examples/generate-object-nested.mjs
```
**Key concepts**: Nested objects, arrays of objects, complex relationships

### 13. Validation Constraints (`generate-object-constraints.mjs`)
**Purpose**: Enforce data quality with validation rules.
```bash
node examples/generate-object-constraints.mjs
```
**Key concepts**: Enums, ranges, patterns, business rules

### 14. Advanced Object Generation (`generate-object-advanced.mjs`)
**Purpose**: Real-world examples of complex object generation.
```bash
node examples/generate-object-advanced.mjs
```
**Key concepts**: Product catalogs, analytics data, form generation

## Testing & Troubleshooting

### 15. Check Authentication (`check-auth.mjs`)
**Purpose**: Verify Google Cloud Code authentication status.
```bash
node examples/check-auth.mjs
```
**Key concepts**: OAuth validation, credential refresh, troubleshooting

### 16. Integration Test (`integration-test.mjs`)
**Purpose**: Comprehensive test suite to verify all features.
```bash
node examples/integration-test.mjs
```
**Key concepts**: Feature verification, error handling, test patterns

### 17. Error Handling (`error-handling.mjs`)
**Purpose**: Demonstrate proper error handling patterns.
```bash
node examples/error-handling.mjs
```
**Key concepts**: Authentication errors, rate limits, retry logic

## Common Patterns

### OAuth Authentication
```javascript
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';

// Uses ~/.gemini/oauth_creds.json automatically
const gemini = createGeminiProvider({
  authType: 'oauth-personal'
});
```

### API Key Authentication
```javascript
const gemini = createGeminiProvider({
  authType: 'api-key',
  apiKey: process.env.GEMINI_API_KEY
});
```

### Message History
```javascript
const messages = [
  { role: 'user', content: 'My name is Alice' },
  { role: 'assistant', content: 'Nice to meet you, Alice!' },
  { role: 'user', content: 'What is my name?' }
];

const { text } = await generateText({
  model: gemini('gemini-3-pro-preview'),
  messages,
});
```

### Custom Timeouts
```javascript
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 60000); // 1 minute

try {
  const { text } = await generateText({
    model: gemini('gemini-3-pro-preview'),
    prompt: 'Complex analysis...',
    abortSignal: controller.signal,
  });
  clearTimeout(timeout);
} catch (error) {
  // Handle timeout
}
```

## Quick Reference

| Example | Primary Use Case | Key Feature |
|---------|-----------------|-------------|
| basic-usage | Getting started | Simple generation |
| streaming | Responsive UIs | Real-time output |
| conversation-history | Chatbots | Context retention |
| logging-default | Default behavior | Warn/error only |
| logging-verbose | Development/debugging | All log levels |
| logging-custom-logger | External integration | Custom logger impl |
| logging-disabled | Silent operation | No logs at all |
| custom-config | Authentication | OAuth vs API key |
| system-messages | Response control | System prompts |
| generate-object-basic | Learning | Structured output |
| generate-object-nested | Complex data | Hierarchical JSON |
| check-auth | Setup | Authentication status |

## Learning Path

1. **Beginners**: `check-auth.mjs` → `basic-usage.mjs` → `streaming.mjs` → `conversation-history.mjs`
2. **Logging**: `logging-default.mjs` → `logging-verbose.mjs` → `logging-custom-logger.mjs` → `logging-disabled.mjs`
3. **Object Generation**: `generate-object-basic.mjs` → `generate-object-nested.mjs` → `generate-object-advanced.mjs`
4. **Advanced**: `system-messages.mjs` → `long-running-tasks.mjs` → `error-handling.mjs`
5. **Testing**: Run `integration-test.mjs` to verify everything works

## Cloud Code Endpoints

This provider uses Google Cloud Code endpoints (https://cloudcode-pa.googleapis.com) through the gemini-cli-core library. The available models include:
- `gemini-3-pro-preview` - Latest next-generation model (Preview) - **Recommended for all examples**
- `gemini-2.5-pro` - Previous generation production-ready model (64K output tokens)
- `gemini-2.5-flash` - Faster, efficient model (64K output tokens)
- And more models as they become available

**Note**: The provider defaults to 64K output tokens to take full advantage of Gemini 2.5's capabilities. You can override this with the `maxTokens` parameter if needed.

## Troubleshooting

If you encounter authentication issues:
1. Run `gemini` and follow setup prompts to authenticate
2. Check `~/.gemini/oauth_creds.json` exists
3. Run `node examples/check-auth.mjs` to verify

If you encounter rate limit errors:
- Add delays between requests if running multiple examples
- Consider reducing the number of concurrent requests
- Check your quota in the Google Cloud Console

For object generation issues:
- Very strict character length constraints (e.g., exactly 60-80 chars) can be challenging
- Consider using ranges or slightly more flexible constraints
- The model may occasionally exceed limits by a few characters
- **Important**: When using `generateObject`, validation failures will throw an error saying "No object generated: could not parse the response"
  - This error message is misleading - the JSON was likely parsed successfully but failed schema validation
  - The actual generated object is available in the error's `text` property
  - For production use with strict schemas, consider using `generateText` with JSON mode for more control over validation

For more details, see the main [README](../README.md).</doc></tutorials><src><doc title="Client" desc="docs page.">import { randomUUID } from 'node:crypto';
import type {
  ContentGenerator,
  ContentGeneratorConfig,
} from '@google/gemini-cli-core';
import {
  createContentGenerator,
  createContentGeneratorConfig,
  AuthType,
} from '@google/gemini-cli-core';
import type { GeminiProviderOptions } from './types';

export interface GeminiClient {
  client: ContentGenerator;
  config: ContentGeneratorConfig;
  sessionId: string;
}

/**
 * Initializes the Gemini client with the provided authentication options
 */
export async function initializeGeminiClient(
  options: GeminiProviderOptions,
  modelId: string
): Promise<GeminiClient> {
  // Map our auth types to Gemini CLI Core auth types
  let authType: AuthType | undefined;

  if (options.authType === 'api-key' || options.authType === 'gemini-api-key') {
    authType = AuthType.USE_GEMINI;
  } else if (options.authType === 'vertex-ai') {
    authType = AuthType.USE_VERTEX_AI;
  } else if (
    options.authType === 'oauth' ||
    options.authType === 'oauth-personal'
  ) {
    authType = AuthType.LOGIN_WITH_GOOGLE;
  } else if (options.authType === 'google-auth-library') {
    // Google Auth Library is not directly supported by AuthType enum
    // We'll need to handle this differently or use a default
    authType = AuthType.USE_GEMINI;
  }

  // Generate a stable session ID for this provider instance
  const sessionId = randomUUID();

  // Phase 1: Core config methods with safe defaults
  const baseConfig = {
    // Required methods (currently working)
    getModel: () => modelId,
    getProxy: () =>
      options.proxy ||
      process.env.HTTP_PROXY ||
      process.env.HTTPS_PROXY ||
      undefined,
    getUsageStatisticsEnabled: () => false, // Disable telemetry by default
    getContentGeneratorConfig: () => ({
      authType: authType, // Keep as AuthType | undefined for consistency
      model: modelId,
      apiKey: 'apiKey' in options ? options.apiKey : undefined,
      vertexai: options.authType === 'vertex-ai' ? true : undefined,
      proxy: options.proxy,
    }),

    // Core safety methods - most likely to be called
    getSessionId: () => sessionId,
    getDebugMode: () => false,
    getTelemetryEnabled: () => false,
    getTargetDir: () => process.cwd(),
    getFullContext: () => false,
    getIdeMode: () => false,
    getCoreTools: () => [],
    getExcludeTools: () => [],
    getMaxSessionTurns: () => 100,
    getFileFilteringRespectGitIgnore: () => true,

    // OAuth-specific methods (required for LOGIN_WITH_GOOGLE auth)
    isBrowserLaunchSuppressed: () => false, // Allow browser launch for OAuth flow
  };

  // Phase 2: Proxy wrapper to catch any unknown method calls
  const configMock = new Proxy(baseConfig, {
    get(target, prop) {
      if (prop in target) {
        return target[prop as keyof typeof target];
      }

      // Log unknown method calls (helps identify what else might be needed)
      if (typeof prop === 'string') {
        // Handle different method patterns
        if (
          prop.startsWith('get') ||
          prop.startsWith('is') ||
          prop.startsWith('has')
        ) {
          if (process.env.DEBUG) {
            console.warn(
              `[ai-sdk-provider-gemini-cli] Unknown config method called: ${prop}()`
            );
          }

          // Return safe defaults based on method prefix and naming patterns
          return () => {
            // Boolean methods (is*, has*)
            if (prop.startsWith('is') || prop.startsWith('has')) {
              return false; // Safe default for boolean checks
            }

            // Getter methods (get*)
            if (prop.startsWith('get')) {
              // Return undefined for most unknown methods (safest default)
              if (prop.includes('Enabled') || prop.includes('Mode')) {
                return false; // Booleans default to false
              }
              if (
                prop.includes('Registry') ||
                prop.includes('Client') ||
                prop.includes('Service')
              ) {
                return undefined; // Objects/services default to undefined
              }
              if (prop.includes('Config') || prop.includes('Options')) {
                return {}; // Config objects default to empty
              }
              if (prop.includes('Command') || prop.includes('Path')) {
                return undefined; // Strings default to undefined
              }
              return undefined; // Default fallback
            }

            return undefined; // Fallback for any other pattern
          };
        }
      }

      return undefined;
    },
  });

  // Create the configuration
  const config = await createContentGeneratorConfig(
    configMock as unknown as Parameters<typeof createContentGeneratorConfig>[0],
    authType
  );

  // Apply additional configuration based on auth type
  if (
    (options.authType === 'api-key' || options.authType === 'gemini-api-key') &&
    options.apiKey
  ) {
    config.apiKey = options.apiKey;
  } else if (options.authType === 'vertex-ai' && options.vertexAI) {
    config.vertexai = true;
    // Note: Vertex AI project/location configuration might need to be
    // handled through environment variables or other means
  }

  // Create content generator - pass the configMock as the second parameter and sessionId
  const client = await createContentGenerator(
    config,
    configMock as unknown as Parameters<typeof createContentGenerator>[1],
    sessionId
  );

  return { client, config, sessionId };
}</doc><doc title="Error" desc="docs page.">import { APICallError, LoadAPIKeyError } from '@ai-sdk/provider';

/**
 * Custom error metadata for Gemini CLI errors
 */
export interface GeminiCLIErrorMetadata {
  code?: string;
  exitCode?: number;
  stderr?: string;
  promptExcerpt?: string;
}

/**
 * Creates an API call error with Gemini-specific metadata
 */
export function createAPICallError({
  message,
  code,
  exitCode,
  stderr,
  promptExcerpt,
  isRetryable = false,
  statusCode = 500,
}: GeminiCLIErrorMetadata & {
  message: string;
  isRetryable?: boolean;
  statusCode?: number;
}): APICallError {
  return new APICallError({
    url: 'gemini-cli-core://command',
    requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : {},
    statusCode,
    responseHeaders: {},
    message,
    data: {
      code,
      exitCode,
      stderr,
    },
    isRetryable,
  });
}

/**
 * Creates an authentication error
 */
export function createAuthenticationError({
  message,
}: {
  message: string;
}): LoadAPIKeyError {
  return new LoadAPIKeyError({
    message,
  });
}

/**
 * Creates a timeout error
 */
export function createTimeoutError({
  message,
  promptExcerpt,
}: {
  message: string;
  promptExcerpt?: string;
}): APICallError {
  return createAPICallError({
    message,
    code: 'TIMEOUT',
    promptExcerpt,
    isRetryable: true,
    statusCode: 504,
  });
}

/**
 * Checks if an error is an authentication error
 */
export function isAuthenticationError(error: unknown): boolean {
  if (error instanceof LoadAPIKeyError) {
    return true;
  }

  if (error instanceof Error) {
    const message = error.message.toLowerCase();
    return (
      message.includes('unauthorized') ||
      message.includes('authentication') ||
      message.includes('api key') ||
      message.includes('credentials')
    );
  }

  return false;
}

/**
 * Checks if an error is a timeout error
 */
export function isTimeoutError(error: unknown): boolean {
  if (error instanceof APICallError) {
    return (
      error.statusCode === 504 ||
      (error.data as GeminiCLIErrorMetadata)?.code === 'TIMEOUT'
    );
  }

  if (error instanceof Error) {
    const message = error.message.toLowerCase();
    return message.includes('timeout') || message.includes('timed out');
  }

  return false;
}

/**
 * Gets error metadata from an error
 */
export function getErrorMetadata(
  error: unknown
): GeminiCLIErrorMetadata | undefined {
  if (error instanceof APICallError) {
    return error.data as GeminiCLIErrorMetadata;
  }

  return undefined;
}

/**
 * Maps Gemini errors to Vercel AI SDK errors (v5 pattern)
 */
export function mapGeminiError(error: unknown): APICallError | LoadAPIKeyError {
  if (error instanceof Error) {
    // Don't wrap abort errors - they should pass through unchanged
    if (error.name === 'AbortError') {
      throw error;
    }

    const message = error.message.toLowerCase();

    // Check for authentication errors
    if (isAuthenticationError(error)) {
      return createAuthenticationError({
        message: error.message,
      });
    }

    // Check for rate limit errors
    if (message.includes('rate limit') || message.includes('quota')) {
      return createAPICallError({
        message: error.message,
        code: 'RATE_LIMIT',
        isRetryable: true,
        statusCode: 429,
      });
    }

    // Check for timeout errors
    if (isTimeoutError(error)) {
      return createTimeoutError({
        message: error.message,
      });
    }

    // Check for model not found (check this before general invalid errors)
    if (
      message.includes('not found') ||
      message.includes('no such model') ||
      (message.includes('model') &&
        (message.includes('invalid') || message.includes('not found')))
    ) {
      return createAPICallError({
        message: error.message,
        code: 'MODEL_NOT_FOUND',
        isRetryable: false,
        statusCode: 404,
      });
    }

    // Check for invalid request errors
    if (message.includes('invalid') || message.includes('bad request')) {
      return createAPICallError({
        message: error.message,
        code: 'INVALID_REQUEST',
        isRetryable: false,
        statusCode: 400,
      });
    }

    // Default to internal server error
    return createAPICallError({
      message: error.message,
      code: 'INTERNAL_ERROR',
      isRetryable: true,
      statusCode: 500,
    });
  }

  // Unknown error type
  return createAPICallError({
    message: 'An unknown error occurred',
    code: 'UNKNOWN_ERROR',
    isRetryable: true,
    statusCode: 500,
  });
}</doc><doc title="Gemini Language Model" desc="docs page.">import { randomUUID } from 'node:crypto';
import type {
  LanguageModelV2,
  LanguageModelV2CallOptions,
  LanguageModelV2CallWarning,
  LanguageModelV2FinishReason,
  LanguageModelV2FunctionTool,
  LanguageModelV2StreamPart,
  LanguageModelV2Content,
  LanguageModelV2Usage,
} from '@ai-sdk/provider';
import type {
  ContentGenerator,
  ContentGeneratorConfig,
} from '@google/gemini-cli-core';
import type {
  GenerateContentParameters,
  GenerateContentConfig,
} from '@google/genai';
import { initializeGeminiClient } from './client';
import { mapPromptToGeminiFormat } from './message-mapper';
import { mapGeminiToolConfig, mapToolsToGeminiFormat } from './tool-mapper';
import { mapGeminiError } from './error';
import type { GeminiProviderOptions, Logger } from './types';
import { getLogger, createVerboseLogger } from './logger';

export interface GeminiLanguageModelOptions {
  modelId: string;
  providerOptions: GeminiProviderOptions;
  settings?: Record<string, unknown> & {
    logger?: Logger | false;
    verbose?: boolean;
  };
}

/**
 * Map Gemini finish reasons to Vercel AI SDK finish reasons
 */
function mapGeminiFinishReason(
  geminiReason?: string
): LanguageModelV2FinishReason {
  switch (geminiReason) {
    case 'STOP':
      return 'stop';
    case 'MAX_TOKENS':
      return 'length';
    case 'SAFETY':
    case 'RECITATION':
      return 'content-filter';
    case 'OTHER':
      return 'other';
    default:
      return 'unknown';
  }
}

/**
 * Prepare generation config with proper handling for JSON mode.
 *
 * When JSON response format is requested WITHOUT a schema, we downgrade to
 * text/plain and emit a warning. This aligns with Claude-code provider behavior
 * and prevents raw fenced JSON from leaking to clients.
 *
 * When a schema IS provided, we use native responseJsonSchema for structured output.
 */
function prepareGenerationConfig(
  options: LanguageModelV2CallOptions,
  settings?: Record<string, unknown>
): {
  generationConfig: GenerateContentConfig;
  warnings: LanguageModelV2CallWarning[];
} {
  const warnings: LanguageModelV2CallWarning[] = [];

  // Extract schema if JSON mode with schema is requested
  const responseFormat = options.responseFormat;
  const isJsonMode = responseFormat?.type === 'json';
  const schema = isJsonMode ? responseFormat.schema : undefined;
  const hasSchema = isJsonMode && schema !== undefined;

  // JSON without schema: downgrade to text/plain with warning
  if (isJsonMode && !hasSchema) {
    warnings.push({
      type: 'unsupported-setting',
      setting: 'responseFormat',
      details:
        'JSON response format without a schema is not supported. Treating as plain text. Provide a schema for structured output.',
    });
  }

  const generationConfig: GenerateContentConfig = {
    temperature:
      options.temperature ?? (settings?.temperature as number | undefined),
    topP: options.topP ?? (settings?.topP as number | undefined),
    topK: options.topK ?? (settings?.topK as number | undefined),
    maxOutputTokens:
      options.maxOutputTokens ??
      (settings?.maxOutputTokens as number | undefined),
    stopSequences: options.stopSequences,
    // Only use application/json when we have a schema to enforce it
    responseMimeType: hasSchema ? 'application/json' : 'text/plain',
    // Pass schema directly to Gemini API for native structured output
    responseJsonSchema: hasSchema ? schema : undefined,
    toolConfig: mapGeminiToolConfig(options),
  };

  return { generationConfig, warnings };
}

export class GeminiLanguageModel implements LanguageModelV2 {
  readonly specificationVersion = 'v2' as const;
  readonly provider = 'gemini-cli-core';
  readonly defaultObjectGenerationMode = 'json' as const;
  readonly supportsImageUrls = false; // CLI Core uses base64 data, not URLs
  readonly supportedUrls = {}; // No native URL support
  readonly supportsStructuredOutputs = true; // Native Gemini responseJsonSchema support

  private contentGenerator?: ContentGenerator;
  private config?: ContentGeneratorConfig;
  private initPromise?: Promise<void>;

  readonly modelId: string;
  readonly settings?: Record<string, unknown>;
  private providerOptions: GeminiProviderOptions;
  private logger: Logger;

  constructor(options: GeminiLanguageModelOptions) {
    this.modelId = options.modelId;
    this.providerOptions = options.providerOptions;
    this.settings = options.settings;

    // Create logger that respects verbose setting
    const baseLogger = getLogger(options.settings?.logger);
    this.logger = createVerboseLogger(
      baseLogger,
      options.settings?.verbose ?? false
    );
  }

  private async ensureInitialized(): Promise<{
    contentGenerator: ContentGenerator;
    config: ContentGeneratorConfig;
  }> {
    if (this.contentGenerator && this.config) {
      return { contentGenerator: this.contentGenerator, config: this.config };
    }

    if (!this.initPromise) {
      this.initPromise = this.initialize();
    }

    await this.initPromise;
    return { contentGenerator: this.contentGenerator!, config: this.config! };
  }

  private async initialize(): Promise<void> {
    try {
      const { client, config } = await initializeGeminiClient(
        this.providerOptions,
        this.modelId
      );
      this.contentGenerator = client;
      this.config = config;
    } catch (error) {
      throw new Error(`Failed to initialize Gemini model: ${String(error)}`);
    }
  }

  /**
   * Non-streaming generation method
   */
  async doGenerate(options: LanguageModelV2CallOptions): Promise<{
    content: LanguageModelV2Content[];
    finishReason: LanguageModelV2FinishReason;
    usage: LanguageModelV2Usage;
    rawCall: {
      rawPrompt: unknown;
      rawSettings: Record<string, unknown>;
    };
    rawResponse?: {
      body?: unknown;
    };
    response?: {
      id?: string;
      timestamp?: Date;
      modelId?: string;
    };
    warnings: LanguageModelV2CallWarning[];
  }> {
    this.logger.debug(
      `[gemini-cli] Starting doGenerate request with model: ${this.modelId}`
    );

    try {
      const { contentGenerator } = await this.ensureInitialized();

      // Map the prompt to Gemini format
      const { contents, systemInstruction } = mapPromptToGeminiFormat(options);

      this.logger.debug(
        `[gemini-cli] Request mode: ${options.responseFormat?.type === 'json' ? 'object-json' : 'regular'}, response format: ${options.responseFormat?.type ?? 'none'}`
      );

      this.logger.debug(
        `[gemini-cli] Converted ${options.prompt.length} messages`
      );

      // Prepare generation config with proper JSON mode handling
      // (downgrades to text/plain with warning if JSON requested without schema)
      const { generationConfig, warnings } = prepareGenerationConfig(
        options,
        this.settings
      );

      // Map tools if provided in regular mode
      let tools;
      if (options.tools) {
        // Filter to only function tools (not provider-defined tools)
        const functionTools = options.tools.filter(
          (tool): tool is LanguageModelV2FunctionTool =>
            tool.type === 'function'
        );
        if (functionTools.length > 0) {
          tools = mapToolsToGeminiFormat(functionTools);
        }
      }

      // Create the request parameters
      const request: GenerateContentParameters = {
        model: this.modelId,
        contents,
        config: {
          ...generationConfig,
          systemInstruction: systemInstruction,
          tools: tools,
        },
      };

      // Set up abort handling
      let abortListener: (() => void) | undefined;
      if (options.abortSignal) {
        // Check if already aborted
        if (options.abortSignal.aborted) {
          const abortError = new Error('Request aborted');
          abortError.name = 'AbortError';
          throw abortError;
        }

        // Set up listener for abort signal
        // LIMITATION: The gemini-cli-core library doesn't expose request cancellation
        // We can only check abort status before/after the request, not cancel in-flight
        abortListener = () => {
          // Track abort state - actual cancellation happens via status checks
        };
        options.abortSignal.addEventListener('abort', abortListener, {
          once: true,
        });
      }

      // Generate content (new signature requires userPromptId)
      let response;
      const startTime = Date.now();
      try {
        this.logger.debug('[gemini-cli] Executing generateContent request');

        response = await contentGenerator.generateContent(
          request,
          randomUUID()
        );

        const duration = Date.now() - startTime;
        this.logger.info(
          `[gemini-cli] Request completed - Duration: ${duration}ms`
        );

        // Check if aborted during generation
        if (options.abortSignal?.aborted) {
          const abortError = new Error('Request aborted');
          abortError.name = 'AbortError';
          throw abortError;
        }
      } finally {
        // Clean up abort listener
        if (options.abortSignal && abortListener) {
          options.abortSignal.removeEventListener('abort', abortListener);
        }
      }

      // Extract the result
      const candidate = response.candidates?.[0];
      const responseContent = candidate?.content;

      // Build content array for v2 format
      const content: LanguageModelV2Content[] = [];

      if (responseContent?.parts) {
        for (const part of responseContent.parts) {
          if (part.text) {
            // With native responseJsonSchema, the output is already clean JSON
            content.push({
              type: 'text',
              text: part.text,
            });
          } else if (part.functionCall) {
            content.push({
              type: 'tool-call',
              toolCallId: randomUUID(),
              toolName: part.functionCall.name || '',
              input: JSON.stringify(part.functionCall.args || {}),
            } as LanguageModelV2Content);
          }
        }
      }

      // Calculate token usage
      const inputTokens = response.usageMetadata?.promptTokenCount || 0;
      const outputTokens = response.usageMetadata?.candidatesTokenCount || 0;
      const totalTokens = inputTokens + outputTokens;

      const usage: LanguageModelV2Usage = {
        inputTokens,
        outputTokens,
        totalTokens,
      };

      this.logger.debug(
        `[gemini-cli] Token usage - Input: ${inputTokens}, Output: ${outputTokens}, Total: ${totalTokens}`
      );

      const finishReason = mapGeminiFinishReason(candidate?.finishReason);
      this.logger.debug(`[gemini-cli] Finish reason: ${finishReason}`);

      return {
        content,
        finishReason,
        usage,
        rawCall: {
          rawPrompt: { contents, systemInstruction, generationConfig, tools },
          rawSettings: generationConfig as Record<string, unknown>,
        },
        rawResponse: {
          body: response,
        },
        response: {
          id: randomUUID(),
          timestamp: new Date(),
          modelId: this.modelId,
        },
        warnings,
      };
    } catch (error) {
      this.logger.debug(
        `[gemini-cli] Error during doGenerate: ${error instanceof Error ? error.message : String(error)}`
      );
      throw mapGeminiError(error);
    }
  }

  /**
   * Streaming generation method
   */
  async doStream(options: LanguageModelV2CallOptions): Promise<{
    stream: ReadableStream<LanguageModelV2StreamPart>;
    rawCall: {
      rawPrompt: unknown;
      rawSettings: Record<string, unknown>;
    };
  }> {
    this.logger.debug(
      `[gemini-cli] Starting doStream request with model: ${this.modelId}`
    );

    try {
      const { contentGenerator } = await this.ensureInitialized();

      // Map the prompt to Gemini format
      const { contents, systemInstruction } = mapPromptToGeminiFormat(options);

      this.logger.debug(
        `[gemini-cli] Stream mode: ${options.responseFormat?.type === 'json' ? 'object-json' : 'regular'}, response format: ${options.responseFormat?.type ?? 'none'}`
      );

      this.logger.debug(
        `[gemini-cli] Converted ${options.prompt.length} messages for streaming`
      );

      // Prepare generation config with proper JSON mode handling
      // (downgrades to text/plain with warning if JSON requested without schema)
      const { generationConfig, warnings } = prepareGenerationConfig(
        options,
        this.settings
      );

      // Map tools if provided in regular mode
      let tools;
      if (options.tools) {
        // Filter to only function tools (not provider-defined tools)
        const functionTools = options.tools.filter(
          (tool): tool is LanguageModelV2FunctionTool =>
            tool.type === 'function'
        );
        if (functionTools.length > 0) {
          tools = mapToolsToGeminiFormat(functionTools);
        }
      }

      // Create the request parameters
      const request: GenerateContentParameters = {
        model: this.modelId,
        contents,
        config: {
          ...generationConfig,
          systemInstruction: systemInstruction,
          tools: tools,
        },
      };

      // Set up abort handling
      let abortListener: (() => void) | undefined;
      if (options.abortSignal) {
        // Check if already aborted
        if (options.abortSignal.aborted) {
          const abortError = new Error('Request aborted');
          abortError.name = 'AbortError';
          throw abortError;
        }

        // Set up listener for abort signal
        // LIMITATION: The gemini-cli-core library doesn't expose stream cancellation
        // We can only check abort status during iteration, not cancel the underlying stream
        abortListener = () => {
          // Track abort state - actual cancellation happens via status checks
        };
        options.abortSignal.addEventListener('abort', abortListener, {
          once: true,
        });
      }

      // Create streaming response (new signature requires userPromptId)
      let streamResponse;
      try {
        this.logger.debug(
          '[gemini-cli] Starting generateContentStream request'
        );

        streamResponse = await contentGenerator.generateContentStream(
          request,
          randomUUID()
        );

        // Check if aborted during stream creation
        if (options.abortSignal?.aborted) {
          const abortError = new Error('Request aborted');
          abortError.name = 'AbortError';
          throw abortError;
        }
      } catch (error) {
        // Clean up abort listener on error
        if (options.abortSignal && abortListener) {
          options.abortSignal.removeEventListener('abort', abortListener);
        }
        throw error;
      }

      // Capture modelId, logger, and warnings for use in stream
      const modelId = this.modelId;
      const logger = this.logger;
      const streamWarnings = warnings;

      // Transform the stream to AI SDK v5 format
      const stream = new ReadableStream<LanguageModelV2StreamPart>({
        async start(controller) {
          try {
            // Check for abort signal in stream
            if (options.abortSignal?.aborted) {
              const abortError = new Error('Request aborted');
              abortError.name = 'AbortError';
              controller.error(abortError);
              return;
            }
            let totalInputTokens = 0;
            let totalOutputTokens = 0;

            // Emit stream-start event with any warnings
            controller.enqueue({
              type: 'stream-start',
              warnings: streamWarnings,
            });

            const streamStartTime = Date.now();
            logger.debug('[gemini-cli] Stream started, processing chunks');

            for await (const chunk of streamResponse) {
              // Check if aborted during streaming
              if (options.abortSignal?.aborted) {
                const abortError = new Error('Request aborted');
                abortError.name = 'AbortError';
                controller.error(abortError);
                break;
              }

              const candidate = chunk.candidates?.[0];
              const content = candidate?.content;

              // Update token counts if available
              if (chunk.usageMetadata) {
                totalInputTokens = chunk.usageMetadata.promptTokenCount || 0;
                totalOutputTokens =
                  chunk.usageMetadata.candidatesTokenCount || 0;
              }

              if (content?.parts) {
                for (const part of content.parts) {
                  if (part.text) {
                    // With native responseJsonSchema, stream text directly
                    // (output is already clean JSON when schema is provided)
                    controller.enqueue({
                      type: 'text-delta',
                      id: randomUUID(),
                      delta: part.text,
                    });
                  } else if (part.functionCall) {
                    // Emit tool call as a single event
                    controller.enqueue({
                      type: 'tool-call',
                      toolCallId: randomUUID(),
                      toolName: part.functionCall.name || '',
                      input: JSON.stringify(part.functionCall.args || {}),
                    });
                  }
                }
              }

              if (candidate?.finishReason) {
                const duration = Date.now() - streamStartTime;
                logger.info(
                  `[gemini-cli] Stream completed - Duration: ${duration}ms`
                );

                logger.debug(
                  `[gemini-cli] Stream token usage - Input: ${totalInputTokens}, Output: ${totalOutputTokens}, Total: ${totalInputTokens + totalOutputTokens}`
                );

                const finishReason = mapGeminiFinishReason(
                  candidate.finishReason
                );
                logger.debug(
                  `[gemini-cli] Stream finish reason: ${finishReason}`
                );

                // Emit response metadata
                controller.enqueue({
                  type: 'response-metadata',
                  id: randomUUID(),
                  timestamp: new Date(),
                  modelId: modelId,
                });

                // Emit finish event
                controller.enqueue({
                  type: 'finish',
                  finishReason,
                  usage: {
                    inputTokens: totalInputTokens,
                    outputTokens: totalOutputTokens,
                    totalTokens: totalInputTokens + totalOutputTokens,
                  },
                });
              }
            }

            logger.debug('[gemini-cli] Stream finalized, closing stream');
            controller.close();
          } catch (error) {
            logger.debug(
              `[gemini-cli] Error during doStream: ${error instanceof Error ? error.message : String(error)}`
            );
            controller.error(mapGeminiError(error));
          } finally {
            // Clean up abort listener
            if (options.abortSignal && abortListener) {
              options.abortSignal.removeEventListener('abort', abortListener);
            }
          }
        },
        cancel: () => {
          // Clean up abort listener on cancel
          if (options.abortSignal && abortListener) {
            options.abortSignal.removeEventListener('abort', abortListener);
          }
        },
      });

      return {
        stream,
        rawCall: {
          rawPrompt: { contents, systemInstruction, generationConfig, tools },
          rawSettings: generationConfig as Record<string, unknown>,
        },
      };
    } catch (error) {
      this.logger.debug(
        `[gemini-cli] Error creating stream: ${error instanceof Error ? error.message : String(error)}`
      );
      throw mapGeminiError(error);
    }
  }
}</doc><doc title="Gemini Provider" desc="docs page.">import type {
  ProviderV2,
  LanguageModelV2,
  EmbeddingModelV2,
  ImageModelV2,
} from '@ai-sdk/provider';
import { NoSuchModelError } from '@ai-sdk/provider';
import { GeminiLanguageModel } from './gemini-language-model';
import type { GeminiProviderOptions } from './types';
import { validateAuthOptions } from './validation';

export interface GeminiProvider extends ProviderV2 {
  (modelId: string, settings?: Record<string, unknown>): LanguageModelV2;
  languageModel(
    modelId: string,
    settings?: Record<string, unknown>
  ): LanguageModelV2;
  chat(modelId: string, settings?: Record<string, unknown>): LanguageModelV2;
  textEmbeddingModel(modelId: string): EmbeddingModelV2<string>;
  imageModel(modelId: string): ImageModelV2;
}

/**
 * Creates a new Gemini provider instance.
 *
 * @param options - Configuration options for the provider
 * @returns A configured provider function
 * @throws Error if authentication options are invalid
 *
 * @example
 * ```typescript
 * // Using API key authentication
 * const gemini = createGeminiProvider({
 *   authType: 'gemini-api-key',
 *   apiKey: process.env.GEMINI_API_KEY
 * });
 *
 * // Use with Vercel AI SDK
 * const model = gemini('gemini-1.5-flash');
 * const result = await generateText({
 *   model,
 *   prompt: 'Hello, world!'
 * });
 * ```
 */
export function createGeminiProvider(
  options: GeminiProviderOptions = {}
): GeminiProvider {
  // Validate authentication options
  const validatedOptions = validateAuthOptions(options);

  // Create the language model factory function
  const createLanguageModel = (
    modelId: string,
    settings?: Record<string, unknown>
  ) => {
    return new GeminiLanguageModel({
      modelId,
      providerOptions: validatedOptions,
      settings: {
        maxOutputTokens: 65536, // 64K output tokens for Gemini 2.5 models
        ...settings,
      },
    });
  };

  // Create the provider function
  const provider = Object.assign(
    function (modelId: string, settings?: Record<string, unknown>) {
      if (new.target) {
        throw new Error(
          'The provider function cannot be called with the new keyword.'
        );
      }

      return createLanguageModel(modelId, settings);
    },
    {
      languageModel: createLanguageModel,
      chat: createLanguageModel,
      textEmbeddingModel: (modelId: string): never => {
        throw new NoSuchModelError({
          modelId,
          modelType: 'textEmbeddingModel',
          message: `Gemini provider does not support text embedding models.`,
        });
      },
      imageModel: (modelId: string): never => {
        throw new NoSuchModelError({
          modelId,
          modelType: 'imageModel',
          message: `Gemini provider does not support image models.`,
        });
      },
    }
  ) as GeminiProvider;

  return provider;
}</doc><doc title="Index" desc="docs page.">// Main exports
export { createGeminiProvider } from './gemini-provider';

// Type exports
export type { GeminiProvider } from './gemini-provider';
export type { GeminiProviderOptions, Logger } from './types';

// Legacy compatibility exports (for backward compatibility)
export { createGeminiProvider as createGeminiCliCoreProvider } from './gemini-provider';
export type { GeminiProvider as GeminiCliCoreProvider } from './gemini-provider';
export type { GeminiProviderOptions as GeminiCliCoreProviderOptions } from './types';

// Re-export types from AI SDK for convenience
export type {
  LanguageModelV2,
  LanguageModelV2FunctionTool,
  LanguageModelV2ToolCall,
  LanguageModelV2FinishReason,
  LanguageModelV2CallOptions,
  LanguageModelV2CallWarning,
  LanguageModelV2StreamPart,
  LanguageModelV2Content,
  LanguageModelV2Usage,
  ProviderV2,
} from '@ai-sdk/provider';</doc><doc title="Logger" desc="docs page.">import type { Logger } from './types.js';

/**
 * Default logger that uses console with level tags.
 */
const defaultLogger: Logger = {
  debug: (message: string) => console.debug(`[DEBUG] ${message}`),
  info: (message: string) => console.info(`[INFO] ${message}`),
  warn: (message: string) => console.warn(`[WARN] ${message}`),
  error: (message: string) => console.error(`[ERROR] ${message}`),
};

/**
 * No-op logger that discards all messages.
 */
const noopLogger: Logger = {
  debug: () => {},
  info: () => {},
  warn: () => {},
  error: () => {},
};

/**
 * Gets the appropriate logger instance based on the provided option.
 *
 * @param logger - Logger configuration: undefined (default console), false (no logging), or custom Logger
 * @returns Logger instance to use for all logging operations
 */
export function getLogger(logger: Logger | false | undefined): Logger {
  if (logger === false) {
    return noopLogger;
  }

  if (logger === undefined) {
    return defaultLogger;
  }

  return logger;
}

/**
 * Creates a verbose-aware logger that only logs debug/info when verbose is enabled.
 * Warn and error are always logged regardless of verbose setting.
 *
 * When verbose is false (default), debug and info calls are suppressed.
 * When verbose is true, all log levels are passed through to the underlying logger.
 *
 * @param logger - The underlying logger to wrap
 * @param verbose - Whether to enable verbose (debug/info) logging. Defaults to false.
 * @returns A logger that filters debug/info based on verbose mode
 *
 * @example
 * ```typescript
 * const baseLogger = getLogger(undefined); // console logger
 * const verboseLogger = createVerboseLogger(baseLogger, true);
 *
 * verboseLogger.debug('This will be logged');
 * verboseLogger.info('This will be logged');
 * verboseLogger.warn('Always logged');
 * verboseLogger.error('Always logged');
 * ```
 */
export function createVerboseLogger(
  logger: Logger,
  verbose: boolean = false
): Logger {
  if (verbose) {
    // When verbose is enabled, pass through all log levels
    return logger;
  }

  // When verbose is disabled, suppress debug and info, but keep warn and error
  return {
    debug: () => {}, // Suppressed in non-verbose mode
    info: () => {}, // Suppressed in non-verbose mode
    warn: logger.warn.bind(logger),
    error: logger.error.bind(logger),
  };
}</doc><doc title="Message Mapper" desc="docs page.">import type {
  LanguageModelV2CallOptions,
  LanguageModelV2FilePart,
  LanguageModelV2Message,
} from '@ai-sdk/provider';
import type { Content, Part } from '@google/genai';

export interface GeminiPromptResult {
  contents: Content[];
  systemInstruction?: Content;
}

/**
 * Maps Vercel AI SDK messages to Gemini format
 *
 * Note: Schema is now passed directly via responseJsonSchema in the generation config,
 * so we no longer inject schema instructions into the prompt.
 */
export function mapPromptToGeminiFormat(
  options: LanguageModelV2CallOptions
): GeminiPromptResult {
  const messages = options.prompt;
  const contents: Content[] = [];
  let systemInstruction: Content | undefined;

  for (const message of messages) {
    switch (message.role) {
      case 'system':
        // Gemini uses a separate systemInstruction field
        systemInstruction = {
          role: 'user',
          parts: [{ text: message.content }],
        };
        break;

      case 'user':
        contents.push(mapUserMessage(message));
        break;

      case 'assistant':
        contents.push(mapAssistantMessage(message));
        break;

      case 'tool': {
        // Tool results in v5 are part of tool messages
        const parts: Part[] = [];
        for (const part of message.content) {
          if (part.type === 'tool-result') {
            parts.push({
              functionResponse: {
                name: part.toolName,
                response: (typeof part.output === 'string'
                  ? { result: part.output }
                  : part.output) as Record<string, unknown>,
              },
            });
          }
        }
        contents.push({
          role: 'user',
          parts,
        });
        break;
      }
    }
  }

  return { contents, systemInstruction };
}

/**
 * Maps a user message to Gemini format
 */
function mapUserMessage(
  message: LanguageModelV2Message & { role: 'user' }
): Content {
  const parts: Part[] = [];

  for (const part of message.content) {
    switch (part.type) {
      case 'text':
        parts.push({ text: part.text });
        break;

      case 'file': {
        // Handle file parts (images, etc.)
        const mediaType = part.mediaType || 'application/octet-stream';
        if (mediaType.startsWith('image/')) {
          parts.push(mapImagePart(part));
        } else {
          throw new Error(`Unsupported file type: ${mediaType}`);
        }
        break;
      }
    }
  }

  return { role: 'user', parts };
}

/**
 * Maps an assistant message to Gemini format
 */
function mapAssistantMessage(
  message: LanguageModelV2Message & { role: 'assistant' }
): Content {
  const parts: Part[] = [];

  for (const part of message.content) {
    switch (part.type) {
      case 'text':
        parts.push({ text: part.text });
        break;

      case 'tool-call':
        // In v5, tool calls have input as an object already
        parts.push({
          functionCall: {
            name: part.toolName,
            args: (part.input || {}) as Record<string, unknown>,
          },
        });
        break;
    }
  }

  return { role: 'model', parts };
}

/**
 * Maps an image part to Gemini format
 */
function mapImagePart(part: LanguageModelV2FilePart): Part {
  if (part.data instanceof URL) {
    throw new Error(
      'URL images are not supported by Gemini CLI Core. Please provide base64-encoded image data.'
    );
  }

  // Extract mime type and base64 data
  const mimeType = part.mediaType || 'image/jpeg';
  let base64Data: string;

  if (typeof part.data === 'string') {
    // Already base64 encoded
    base64Data = part.data;
  } else if (part.data instanceof Uint8Array) {
    // Convert Uint8Array to base64
    base64Data = Buffer.from(part.data).toString('base64');
  } else {
    throw new Error('Unsupported image format');
  }

  return {
    inlineData: {
      mimeType,
      data: base64Data,
    },
  };
}</doc><doc title="Tool Mapper" desc="docs page.">import type {
  LanguageModelV2CallOptions,
  LanguageModelV2FunctionTool,
  LanguageModelV2ToolChoice,
} from '@ai-sdk/provider';
import {
  Tool,
  FunctionDeclaration,
  Schema,
  ToolConfig,
  FunctionCallingConfigMode,
} from '@google/genai';
import { z } from 'zod';

// Type for JSON Schema objects with common properties
interface JsonSchemaObject {
  $schema?: string;
  $ref?: string;
  $defs?: unknown;
  definitions?: unknown;
  properties?: Record<string, unknown>;
  items?: unknown;
  additionalProperties?: unknown;
  allOf?: unknown[];
  anyOf?: unknown[];
  oneOf?: unknown[];
  [key: string]: unknown;
}

/**
 * Maps Vercel AI SDK tools to Gemini format
 */
export function mapToolsToGeminiFormat(
  tools: LanguageModelV2FunctionTool[]
): Tool[] {
  const functionDeclarations: FunctionDeclaration[] = [];

  for (const tool of tools) {
    functionDeclarations.push({
      name: tool.name,
      description: tool.description,
      parameters: convertToolParameters(tool.inputSchema),
    });
  }

  return [{ functionDeclarations }];
}

/**
 * Attempts to convert a Zod schema to JSON Schema using available methods
 */
function convertZodToJsonSchema(zodSchema: z.ZodSchema): unknown {
  // Try Zod v4's native toJSONSchema function first (if available)
  const zodWithToJSONSchema = z as unknown as {
    toJSONSchema?: (schema: z.ZodSchema) => unknown;
  };

  if (
    zodWithToJSONSchema.toJSONSchema &&
    typeof zodWithToJSONSchema.toJSONSchema === 'function'
  ) {
    try {
      // Zod v4 uses z.toJSONSchema(schema) as a standalone function
      return zodWithToJSONSchema.toJSONSchema(zodSchema);
    } catch {
      // Method exists but failed, try fallback
    }
  }

  // Try zod-to-json-schema for Zod v3 compatibility
  try {
    // Lazy load zod-to-json-schema to avoid import errors with Zod v4
    // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
    const zodToJsonSchemaModule = require('zod-to-json-schema');
    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
    return zodToJsonSchemaModule.zodToJsonSchema(zodSchema);
  } catch {
    // zod-to-json-schema not available or not compatible
  }

  // No conversion method available
  console.warn(
    'Unable to convert Zod schema to JSON Schema. ' +
      'For Zod v3, install zod-to-json-schema. ' +
      'For Zod v4, use z.toJSONSchema() function.'
  );

  // Return a basic object schema as fallback
  return { type: 'object' };
}

/**
 * Converts tool parameters from Zod schema or JSON schema to Gemini format
 */
function convertToolParameters(parameters: unknown): Schema {
  // If it's already a plain object (JSON schema), clean it
  if (isJsonSchema(parameters)) {
    return cleanJsonSchema(parameters as JsonSchemaObject) as Schema;
  }

  // If it's a Zod schema, convert to JSON schema first
  if (isZodSchema(parameters)) {
    const jsonSchema = convertZodToJsonSchema(parameters as z.ZodSchema);
    return cleanJsonSchema(jsonSchema as JsonSchemaObject) as Schema;
  }

  // Return a basic schema if we can't identify the format
  return parameters as Schema;
}

/**
 * Checks if an object is a JSON schema
 */
function isJsonSchema(obj: unknown): boolean {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    ('type' in obj || 'properties' in obj || '$schema' in obj)
  );
}

/**
 * Checks if an object is a Zod schema
 */
function isZodSchema(obj: unknown): obj is z.ZodTypeAny {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    '_def' in obj &&
    typeof (obj as z.ZodTypeAny)._def === 'object'
  );
}

/**
 * Cleans JSON schema for Gemini compatibility
 * Removes $schema and other metadata that Gemini doesn't support
 */
function cleanJsonSchema(schema: JsonSchemaObject): JsonSchemaObject {
  if (typeof schema !== 'object' || schema === null) {
    return schema;
  }

  const cleaned = { ...schema };

  // Remove $schema property
  delete cleaned.$schema;
  delete cleaned.$ref;
  delete cleaned.$defs;
  delete cleaned.definitions;

  // Recursively clean nested schemas
  if (cleaned.properties && typeof cleaned.properties === 'object') {
    const cleanedProps: Record<string, unknown> = {};
    for (const [key, value] of Object.entries(cleaned.properties)) {
      cleanedProps[key] = cleanJsonSchema(value as JsonSchemaObject);
    }
    cleaned.properties = cleanedProps;
  }

  if (cleaned.items) {
    cleaned.items = cleanJsonSchema(cleaned.items as JsonSchemaObject);
  }

  if (
    cleaned.additionalProperties &&
    typeof cleaned.additionalProperties === 'object'
  ) {
    cleaned.additionalProperties = cleanJsonSchema(
      cleaned.additionalProperties as JsonSchemaObject
    );
  }

  // Clean arrays
  for (const key of ['allOf', 'anyOf', 'oneOf'] as const) {
    const arrayProp = cleaned[key];
    if (Array.isArray(arrayProp)) {
      cleaned[key] = arrayProp.map((item) =>
        cleanJsonSchema(item as JsonSchemaObject)
      );
    }
  }

  return cleaned;
}

/**
 * Maps Vercel AI SDK tool config options to Gemini format
 */
export function mapGeminiToolConfig(
  options: LanguageModelV2CallOptions
): ToolConfig | undefined {
  if (options.toolChoice) {
    // Restrict allowed function names when a specific tool is forced.
    // Gemini expects that when forcing a tool call, the function name is
    // provided via `allowedFunctionNames` while `mode` is set to ANY.
    const allowedFunctionNames =
      options.toolChoice.type === 'tool'
        ? [options.toolChoice.toolName]
        : undefined;

    return {
      functionCallingConfig: {
        allowedFunctionNames,
        mode: mapToolChoiceToGeminiFormat(options.toolChoice),
      },
    };
  }
  return undefined;
}

function mapToolChoiceToGeminiFormat(
  toolChoice: LanguageModelV2ToolChoice
): FunctionCallingConfigMode {
  switch (toolChoice.type) {
    case 'auto':
      return FunctionCallingConfigMode.AUTO;
    case 'none':
      return FunctionCallingConfigMode.NONE;
    case 'required':
    case 'tool':
      return FunctionCallingConfigMode.ANY;
    default:
      // this should never happen if types are correct
      return FunctionCallingConfigMode.MODE_UNSPECIFIED;
  }
}</doc><doc title="Types" desc="docs page.">import type { GoogleAuth } from 'google-auth-library';

/**
 * Base options available for all authentication types
 */
export interface BaseProviderOptions {
  /**
   * HTTP proxy URL to use for requests
   * Can also be set via HTTP_PROXY or HTTPS_PROXY environment variables
   */
  proxy?: string;
}

/**
 * Provider options for configuring Gemini authentication and behavior
 */
export type GeminiProviderOptions =
  | (GeminiApiKeyAuth & BaseProviderOptions)
  | (VertexAIAuth & BaseProviderOptions)
  | (OAuthAuth & BaseProviderOptions)
  | (GoogleAuthLibraryAuth & BaseProviderOptions)
  | ({ authType?: undefined } & BaseProviderOptions);

/**
 * Gemini API key authentication (supports both AI SDK standard and Gemini-specific auth types)
 */
export interface GeminiApiKeyAuth {
  authType: 'api-key' | 'gemini-api-key';
  apiKey?: string;
}

/**
 * Vertex AI authentication
 */
export interface VertexAIAuth {
  authType: 'vertex-ai';
  vertexAI: {
    projectId: string;
    location: string;
    apiKey?: string;
  };
}

/**
 * OAuth authentication (personal or service account)
 */
export interface OAuthAuth {
  authType: 'oauth' | 'oauth-personal';
  cacheDir?: string;
}

/**
 * Google Auth Library authentication
 */
export interface GoogleAuthLibraryAuth {
  authType: 'google-auth-library';
  googleAuth?: GoogleAuth;
  googleAuthClient?: unknown; // For backward compatibility
}

/**
 * Logger interface for provider diagnostics and debugging.
 *
 * Supports four log levels:
 * - `debug`: Detailed execution tracing (request/response, tool calls, stream events)
 * - `info`: General execution flow information (session initialization, completion)
 * - `warn`: Warnings about configuration issues or unexpected behavior
 * - `error`: Error messages for failures and exceptions
 *
 * When implementing a custom logger, all four methods must be provided.
 *
 * @example
 * ```typescript
 * const customLogger: Logger = {
 *   debug: (msg) => myLogger.debug(msg),
 *   info: (msg) => myLogger.info(msg),
 *   warn: (msg) => myLogger.warn(msg),
 *   error: (msg) => myLogger.error(msg),
 * };
 * ```
 */
export interface Logger {
  /**
   * Log detailed execution tracing (only shown when verbose mode is enabled).
   * Used for request/response details, tool calls, stream events, and token usage.
   */
  debug(message: string): void;

  /**
   * Log general execution flow information (only shown when verbose mode is enabled).
   * Used for session initialization, request completion, and major state transitions.
   */
  info(message: string): void;

  /**
   * Log warnings about configuration issues or unexpected behavior.
   * Always shown regardless of verbose mode setting.
   */
  warn(message: string): void;

  /**
   * Log error messages for failures and exceptions.
   * Always shown regardless of verbose mode setting.
   */
  error(message: string): void;
}</doc><doc title="Validation" desc="docs page.">import type { GeminiProviderOptions } from './types';

/**
 * Validates the authentication options for the Gemini provider.
 * Ensures that the provided configuration has valid authentication credentials.
 *
 * @param options - The provider options to validate
 * @returns The validated options
 * @throws Error if authentication configuration is invalid
 */
export function validateAuthOptions(
  options: GeminiProviderOptions = {}
): GeminiProviderOptions {
  // Default to oauth-personal if no authType specified
  const authType = options.authType || 'oauth-personal';

  // Validate based on auth type
  switch (authType) {
    case 'api-key':
    case 'gemini-api-key':
      if (!('apiKey' in options) || !options.apiKey) {
        throw new Error(`API key is required for ${authType} auth type`);
      }
      return { ...options, authType };

    case 'vertex-ai':
      if ('vertexAI' in options && options.vertexAI) {
        if (
          !options.vertexAI.projectId ||
          options.vertexAI.projectId.trim() === ''
        ) {
          throw new Error('Project ID is required for vertex-ai auth type');
        }
        if (
          !options.vertexAI.location ||
          options.vertexAI.location.trim() === ''
        ) {
          throw new Error('Location is required for vertex-ai auth type');
        }
      } else {
        throw new Error(
          'Vertex AI configuration is required for vertex-ai auth type'
        );
      }
      return { ...options, authType };

    case 'oauth':
    case 'oauth-personal':
      // No additional validation needed for oauth
      return { ...options, authType };

    case 'google-auth-library':
      if (!('googleAuth' in options) || !options.googleAuth) {
        throw new Error(
          'Google Auth Library instance is required for google-auth-library auth type'
        );
      }
      return { ...options, authType };

    default:
      throw new Error(`Invalid auth type: ${String(authType)}`);
  }
}</doc></src></project>


## Links discovered
- [ai-sdk-v5/GUIDE.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/ai-sdk-v5/GUIDE.md)
- [ai-sdk-v5/BREAKING_CHANGES.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/ai-sdk-v5/BREAKING_CHANGES.md)
- [ai-sdk-v5/TROUBLESHOOTING.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/ai-sdk-v5/TROUBLESHOOTING.md)
- [Project Structure](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/project-structure.md)
- [Authentication Options](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/gemini-cli-auth-options.md)
- [Language Model V2 Implementation](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/language-model-v2-implementation.md)
- [Tool Schema Mapping](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/tool-schema-mapping.md)
- [Zod to Gemini Mapping](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/zod-to-gemini-mapping.md)
- [examples directory](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/examples.md)
- [examples](https://github.com/AcidicSoil/llms-txt-registry/blob/main/examples.md)
- [GUIDE.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/GUIDE.md)
- [TROUBLESHOOTING.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/TROUBLESHOOTING.md)
- [BREAKING_CHANGES.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/BREAKING_CHANGES.md)
- [README](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/README.md)

--- docs/ai-sdk-provider-gemini-cli/ai-sdk-provider-gemini-cli-llms-full.txt ---
# llms-full (private-aware)
> Built from GitHub files and website pages. Large files may be truncated.

--- docs/README.md ---
# AI SDK Provider for Gemini CLI - Technical Documentation

This directory contains technical documentation for the AI SDK Provider for Gemini CLI implementation.

## AI SDK v5 Documentation

This provider is compatible with Vercel AI SDK v5. For v5-specific documentation, see:

- **[ai-sdk-v5/GUIDE.md](ai-sdk-v5/GUIDE.md)** - Comprehensive usage guide for v5
- **[ai-sdk-v5/BREAKING_CHANGES.md](ai-sdk-v5/BREAKING_CHANGES.md)** - Breaking changes and migration guide from v4
- **[ai-sdk-v5/TROUBLESHOOTING.md](ai-sdk-v5/TROUBLESHOOTING.md)** - Common issues and solutions for v5

## Documentation Overview

### 1. [Project Structure](./project-structure.md)
Complete codebase map showing the organization of source files, examples, and documentation.

### 2. [Authentication Options](./gemini-cli-auth-options.md)
Comprehensive guide to the three authentication methods supported by `@google/gemini-cli-core`:
- OAuth with Google Personal Account (`oauth-personal`)
- Gemini API Key (`gemini-api-key`)
- Vertex AI (`vertex-ai`)

### 3. [Language Model V2 Implementation](./language-model-v2-implementation.md)
Detailed specification of the Vercel AI SDK Language Model V2 interface implementation for v5:
- Core interfaces and types
- Message format specifications
- Tool calling interfaces
- Implementation patterns

### 4. [Tool Schema Mapping](./tool-schema-mapping.md)
Guide for mapping between Vercel AI SDK's tool schemas and Gemini's FunctionDeclaration format:
- Type mapping tables
- Implementation approach
- Unsupported features
- Testing considerations

### 5. [Zod to Gemini Mapping](./zod-to-gemini-mapping.md)
Comprehensive mapping between Zod schemas and Gemini's Schema format:
- Type conversions
- Constraint mappings
- Special case handling
- Implementation guidelines

## Quick Reference

### Authentication Setup
```typescript
// OAuth (default)
const gemini = createGeminiProvider({
  authType: 'oauth-personal'
});

// API Key
const gemini = createGeminiProvider({
  authType: 'gemini-api-key',
  apiKey: process.env.GEMINI_API_KEY
});
```

### Supported Models
- `gemini-3-pro-preview` - Latest next-generation model (Preview)
- `gemini-2.5-pro` - Previous generation production-ready model (64K output tokens)
- `gemini-2.5-flash` - Faster, efficient model (64K output tokens)

### Key Features
- ✅ Text generation and streaming
- ✅ System instructions
- ✅ Object generation with Zod schemas
- ✅ Tool calling (function calls)
- ✅ Multimodal inputs (text and images)
- ✅ Conversation history
- ✅ Abort signal support

## Architecture Notes

The provider implements a direct integration with Google's Cloud Code endpoints through the `@google/gemini-cli-core` library. This ensures:
- Native OAuth support with cached credentials
- Direct access to Gemini models
- Optimal performance without intermediate layers
- Full compatibility with Vercel AI SDK v5 patterns

For implementation examples, see the [examples directory](../examples/).

## Links discovered
- [ai-sdk-v5/GUIDE.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/GUIDE.md)
- [ai-sdk-v5/BREAKING_CHANGES.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/BREAKING_CHANGES.md)
- [ai-sdk-v5/TROUBLESHOOTING.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/TROUBLESHOOTING.md)
- [Project Structure](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/./project-structure.md)
- [Authentication Options](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/./gemini-cli-auth-options.md)
- [Language Model V2 Implementation](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/./language-model-v2-implementation.md)
- [Tool Schema Mapping](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/./tool-schema-mapping.md)
- [Zod to Gemini Mapping](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/./zod-to-gemini-mapping.md)
- [examples directory](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/../examples/)

--- docs/dependency-notes.md ---
# Dependency Version Pinning Notes

## @google/gemini-cli-core

**Current Version:** 0.1.22 (EXACT - no caret or tilde)

### Why Exact Version Pinning?

The `@google/gemini-cli-core` package has been introducing breaking changes in patch versions, which violates semantic versioning principles. Examples of breaking changes observed:

### Breaking Changes Timeline

| Version | Release Date | Breaking Changes |
|---------|-------------|------------------|
| 0.1.12 | 2025-07-13 | Baseline version |
| 0.1.13 | 2025-07-19 | Unknown - worked with original code |
| 0.1.14 | 2025-07-25 | Potential breaking changes introduced |
| 0.1.15 | 2025-07-30 | - |
| 0.1.16 | 2025-08-02 | - |
| 0.1.17 | 2025-08-05 | - |
| 0.1.18 | 2025-08-06 | - |
| 0.1.19 | 2025-08-12 | - |
| 0.1.20 | 2025-08-13 | - |
| 0.1.21 | 2025-08-14 | Added telemetry tracking (session events, install IDs) |
| 0.1.22 | 2025-08-18 | Added session ID support |

### Specific Breaking Changes (0.1.13 → 0.1.22)

1. **Config Object Requirements:**
   - Added required `getUsageStatisticsEnabled()` method to config object
   - This method is used for telemetry control (introduced around v0.1.21)

2. **ContentGenerator Method Signatures:**
   - `generateContent()` now requires `userPromptId: string` as second parameter
   - `generateContentStream()` now requires `userPromptId: string` as second parameter
   - These are used for API request logging and telemetry

3. **Factory Function Changes:**
   - `createContentGenerator()` now accepts optional third parameter `sessionId`
   - Used for session tracking (added in v0.1.22)

### Evidence from Source Code

From `google-gemini/gemini-cli` repository, the current implementation shows:
```typescript
// Method calls now require prompt_id
contentGenerator.generateContent(request, prompt_id)
contentGenerator.generateContentStream(request, prompt_id)
```

These changes were made without incrementing the minor or major version, violating semantic versioning where:
- Patch versions (0.0.X) should only contain backwards-compatible bug fixes
- Minor versions (0.X.0) should contain backwards-compatible functionality
- Major versions (X.0.0) should contain breaking changes

### Version Compatibility Matrix

| ai-sdk-provider-gemini-cli | @google/gemini-cli-core | Status |
|---------------------------|------------------------|---------|
| 0.1.0 - 0.1.1            | ~0.1.13                | ❌ Broken with 0.1.22 |
| 0.1.2+                   | 0.1.22 (exact)         | ✅ Working |
| 1.0.0+                   | 0.1.21                 | ❌ Missing 0.1.22 fixes |
| 1.1.0+                   | 0.1.22 (exact)         | ✅ Working |

### Upgrade Strategy

Before upgrading `@google/gemini-cli-core`:

1. Review the changelog for breaking changes (if available)
2. Test thoroughly with the new version
3. Update our code to handle any breaking changes
4. Update this document with new compatibility information
5. Consider maintaining multiple versions if needed for backward compatibility

### Current Implementation: Hybrid Approach

We've implemented a robust hybrid solution that protects against future breaking changes:

#### Phase 1: Core Safety Methods ✅
- Implemented 14 commonly-used config methods with safe defaults
- Covers telemetry, session, debug, and file handling methods
- Provides immediate protection against known breaking changes

#### Phase 2: Proxy Safety Net ✅
- Proxy wrapper catches ALL unknown method calls
- Returns intelligent defaults based on method naming patterns
- Prevents runtime errors from missing methods

#### Phase 3: Debug Logging ✅
- Set `DEBUG=true` environment variable to log unknown method calls
- Helps identify which methods are actually used in practice
- Guides future implementation decisions

### How the Proxy Works

```typescript
// Unknown methods are caught and handled gracefully:
config.getSomeNewMethod() // Returns safe default, logs if DEBUG=true

// Smart defaults based on naming patterns:
- is* methods → false (boolean checks)
- has* methods → false (capability checks)
- get*Enabled/get*Mode methods → false
- get*Registry/get*Client/get*Service methods → undefined  
- get*Config/get*Options methods → {}
- get*Command/get*Path methods → undefined
- All others → undefined
```

### OAuth-Specific Methods

The config includes critical OAuth methods required for LOGIN_WITH_GOOGLE authentication:
- `isBrowserLaunchSuppressed()` → returns `false` (allows browser launch for OAuth flow)

### Benefits

1. **Future-proof**: New methods in gemini-cli-core won't break the integration
2. **Observable**: Debug logging shows what's actually being called
3. **Maintainable**: Only implement methods that are actually used
4. **Safe**: All unknown methods return appropriate defaults

### Recommendation

Until Google/Gemini follows proper semantic versioning:

1. **Keep exact version pinning** - `"0.1.22"` without caret
2. **Monitor debug logs** - Track which methods are actually called
3. **Test thoroughly** before any version updates
4. **Use the Proxy pattern** - Provides safety net for unknown methods

### Related Issues

- Initial compatibility issue discovered: August 2025
- Breaking changes were introduced without major version bump
- No official migration guide provided by Google

### Contact

For questions about version compatibility, please open an issue on the repository.

--- docs/gemini-cli-auth-options.md ---
# @google/gemini-cli-core Authentication Options

Based on my analysis of the `@google/gemini-cli-core` package, here are the supported authentication options:

## Authentication Types

The core package supports three authentication methods, defined in the `AuthType` enum:

```typescript
export enum AuthType {
  LOGIN_WITH_GOOGLE = 'oauth-personal',
  USE_GEMINI = 'gemini-api-key',
  USE_VERTEX_AI = 'vertex-ai'
}
```

## 1. OAuth with Google Personal Account (`oauth-personal`)

- **Auth Type**: `AuthType.LOGIN_WITH_GOOGLE`
- **How it works**: Uses OAuth2 flow with Google authentication
- **Client ID**: `681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com`
- **Scopes**: 
  - `https://www.googleapis.com/auth/cloud-platform`
  - `https://www.googleapis.com/auth/userinfo.email`
  - `https://www.googleapis.com/auth/userinfo.profile`
- **Credentials cached at**: `~/.gemini/oauth_creds.json`
- **No API key required** - uses OAuth tokens instead

## 2. API Key Authentication

This provider supports both AI SDK standard and Gemini-specific auth types:

### AI SDK Standard (`api-key`) - Recommended
- **Auth Type**: `'api-key'` (AI SDK compliant)
- **Environment Variable**: `GEMINI_API_KEY`
- **How it works**: Direct API key authentication with Gemini service
- **Used with**: `GoogleGenAI` client from `@google/genai` package
- **Maps to**: `AuthType.USE_GEMINI`

### Gemini-Specific (`gemini-api-key`) - Alternative
- **Auth Type**: `'gemini-api-key'` (Gemini-specific)
- **Environment Variable**: `GEMINI_API_KEY`
- **How it works**: Same as above, alternative naming
- **Maps to**: `AuthType.USE_GEMINI`

## 3. Vertex AI (`vertex-ai`)

- **Auth Type**: `AuthType.USE_VERTEX_AI`
- **Environment Variables Required**:
  - `GOOGLE_API_KEY` - The API key for authentication
  - `GOOGLE_CLOUD_PROJECT` - The GCP project ID
  - `GOOGLE_CLOUD_LOCATION` - The GCP location/region
- **How it works**: Uses Vertex AI endpoint with API key authentication
- **Used with**: `GoogleGenAI` client with `vertexai: true` flag

## Client Initialization

The `GeminiClient` is initialized with a `Config` object that includes authentication configuration:

```typescript
// Create content generator config with auth type
const contentConfig = await createContentGeneratorConfig(
  model,
  authType,
  config
);

// Initialize the client
const geminiClient = new GeminiClient(config);
await geminiClient.initialize(contentConfig);
```

## Content Generator Configuration

The `ContentGeneratorConfig` interface includes:

```typescript
export type ContentGeneratorConfig = {
  model: string;
  apiKey?: string;
  vertexai?: boolean;
  authType?: AuthType | undefined;
};
```

## Additional Configuration

- **Proxy Support**: The client supports HTTP proxy configuration via the `proxy` parameter
- **Model Selection**: The model can be specified during initialization and changed at runtime
- **Flash Fallback**: OAuth users can fallback to Flash model when hitting rate limits

## Usage Example

```typescript
import { Config, GeminiClient, AuthType } from '@google/gemini-cli-core';

// Example with Gemini API Key
const config = new Config({
  sessionId: 'unique-session-id',
  targetDir: '/path/to/project',
  cwd: process.cwd(),
  model: 'gemini-2.0-flash-exp',
  debugMode: false
});

// Initialize with specific auth type
await config.refreshAuth(AuthType.USE_GEMINI);

// Get the client
const client = config.getGeminiClient();
```

## Authentication Setup

### For OAuth Authentication
```bash
# Initial setup - run and follow interactive prompts
gemini

# Or change auth method inside CLI
/auth
```

### For API Key Authentication
```bash
# Get your API key from Google AI Studio
export GEMINI_API_KEY="your-api-key-here"

# Or set in .gemini/.env file
mkdir -p .gemini
echo 'GEMINI_API_KEY="your-api-key"' >> .gemini/.env
```

### For Vertex AI Authentication
```bash
export GOOGLE_CLOUD_PROJECT="your-project-id"
export GOOGLE_CLOUD_LOCATION="us-central1"
export GOOGLE_GENAI_USE_VERTEXAI=true
export GEMINI_API_KEY="your-api-key"
```

## Key Points

1. **OAuth authentication** provides a seamless experience without requiring API keys
2. **API key authentication** supports both AI SDK standard (`'api-key'`) and Gemini-specific (`'gemini-api-key'`) auth types
3. **Credentials are cached** for OAuth to avoid repeated authentication
4. **The authentication type must be specified** when initializing the client
5. **Environment variables are checked** automatically based on the auth type
6. **Model selection is handled** differently for different auth types (with fallback logic for API keys)
7. **No "gemini auth login" command exists** - use `gemini` for interactive setup or `/auth` inside CLI

--- docs/language-model-v2-implementation.md ---
# LanguageModelV2 Implementation Summary for AI SDK v5

## Overview

The `doGenerate` and `doStream` methods are the core generation methods that all Language Model V2 providers must implement for AI SDK v5 compatibility. These methods handle standardized prompts and options, call the underlying model API, and return standardized results.

## Key Interfaces and Types

### 1. LanguageModelV2 Interface

The main interface that providers must implement for v5:

```typescript
export type LanguageModelV2 = {
  readonly specificationVersion: 'v2';
  readonly provider: string;
  readonly modelId: string;
  readonly defaultObjectGenerationMode: 'json' | 'tool' | undefined;
  readonly supportsImageUrls?: boolean;
  readonly supportsStructuredOutputs?: boolean;
  
  doGenerate(options: LanguageModelV1CallOptions): PromiseLike<{
    text?: string;
    reasoning?: string | Array<...>;
    files?: Array<{ data: string | Uint8Array; mimeType: string }>;
    toolCalls?: Array<LanguageModelV1FunctionToolCall>;
    finishReason: LanguageModelV1FinishReason;
    usage: { promptTokens: number; completionTokens: number };
    rawCall: { rawPrompt: unknown; rawSettings: Record<string, unknown> };
    rawResponse?: { headers?: Record<string, string>; body?: unknown };
    request?: { body?: string };
    response?: { id?: string; timestamp?: Date; modelId?: string };
    warnings?: LanguageModelV1CallWarning[];
    providerMetadata?: LanguageModelV1ProviderMetadata;
    sources?: LanguageModelV1Source[];
    logprobs?: LanguageModelV1LogProbs;
  }>;
  
  doStream(options: LanguageModelV1CallOptions): PromiseLike<{
    stream: ReadableStream<LanguageModelV1StreamPart>;
    // ... other properties
  }>;
};
```

### 2. LanguageModelV2CallOptions

The options passed to doGenerate and doStream in v5:

```typescript
export type LanguageModelV1CallOptions = LanguageModelV1CallSettings & {
  inputFormat: 'messages' | 'prompt';
  mode:
    | {
        type: 'regular';
        tools?: Array<LanguageModelV1FunctionTool | LanguageModelV1ProviderDefinedTool>;
        toolChoice?: LanguageModelV1ToolChoice;
      }
    | {
        type: 'object-json';
        schema?: JSONSchema7;
        name?: string;
        description?: string;
      }
    | {
        type: 'object-tool';
        tool: LanguageModelV1FunctionTool;
      };
  prompt: LanguageModelV1Prompt;
  providerMetadata?: LanguageModelV1ProviderMetadata;
};
```

### 3. LanguageModelV1CallSettings

Common generation settings:

```typescript
export type LanguageModelV1CallSettings = {
  maxTokens?: number;
  temperature?: number;
  stopSequences?: string[];
  topP?: number;
  topK?: number;
  presencePenalty?: number;
  frequencyPenalty?: number;
  responseFormat?: 
    | { type: 'text' }
    | { 
        type: 'json'; 
        schema?: JSONSchema7;
        name?: string;
        description?: string;
      };
  seed?: number;
  abortSignal?: AbortSignal;
  headers?: Record<string, string | undefined>;
};
```

### 4. LanguageModelV1Prompt

The standardized prompt format:

```typescript
export type LanguageModelV1Prompt = Array<LanguageModelV1Message>;

export type LanguageModelV1Message = 
  | {
      role: 'system';
      content: string;
    }
  | {
      role: 'user';
      content: Array<
        | LanguageModelV1TextPart
        | LanguageModelV1ImagePart
        | LanguageModelV1FilePart
      >;
    }
  | {
      role: 'assistant';
      content: Array<
        | LanguageModelV1TextPart
        | LanguageModelV1FilePart
        | LanguageModelV1ReasoningPart
        | LanguageModelV1RedactedReasoningPart
        | LanguageModelV1ToolCallPart
      >;
    }
  | {
      role: 'tool';
      content: Array<LanguageModelV1ToolResultPart>;
    };
```

### 5. Content Part Types

#### Text Part
```typescript
interface LanguageModelV1TextPart {
  type: 'text';
  text: string;
  providerMetadata?: LanguageModelV1ProviderMetadata;
}
```

#### Image Part
```typescript
interface LanguageModelV1ImagePart {
  type: 'image';
  image: Uint8Array | URL;
  mimeType?: string;
  providerMetadata?: LanguageModelV1ProviderMetadata;
}
```

#### Tool Call Part
```typescript
interface LanguageModelV1ToolCallPart {
  type: 'tool-call';
  toolCallId: string;
  toolName: string;
  args: unknown;
  providerMetadata?: LanguageModelV1ProviderMetadata;
}
```

#### Tool Result Part
```typescript
interface LanguageModelV1ToolResultPart {
  type: 'tool-result';
  toolCallId: string;
  toolName: string;
  result: unknown;
  isError?: boolean;
  content?: Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType?: string }>;
  providerMetadata?: LanguageModelV1ProviderMetadata;
}
```

### 6. Tool-Related Types

#### Function Tool Definition
```typescript
export type LanguageModelV1FunctionTool = {
  type: 'function';
  name: string;
  description?: string;
  parameters: JSONSchema7;
};
```

#### Tool Call Result
```typescript
export type LanguageModelV1FunctionToolCall = {
  toolCallType: 'function';
  toolCallId: string;
  toolName: string;
  args: string; // Stringified JSON
};
```

#### Tool Choice
```typescript
export type LanguageModelV1ToolChoice =
  | { type: 'auto' }
  | { type: 'none' }
  | { type: 'required' }
  | { type: 'tool'; toolName: string };
```

### 7. Result Types

#### Finish Reason
```typescript
export type LanguageModelV1FinishReason =
  | 'stop'           // model generated stop sequence
  | 'length'         // model generated maximum number of tokens
  | 'content-filter' // content filter violation stopped the model
  | 'tool-calls'     // model triggered tool calls
  | 'error'          // model stopped because of an error
  | 'other'          // model stopped for other reasons
  | 'unknown';       // the model has not transmitted a finish reason
```

#### Call Warning
```typescript
export type LanguageModelV1CallWarning = 
  | {
      type: 'unsupported-setting';
      setting: 'temperature' | 'maxTokens' | 'topP' | 'topK' | 'presencePenalty' | 'frequencyPenalty' | 'stopSequences' | 'seed';
      details?: string;
    }
  | {
      type: 'other';
      message: string;
    };
```

## Implementation Pattern

Based on the Claude Code provider example, here's the typical implementation pattern:

1. **Parse and validate options**
   - Extract settings from `LanguageModelV1CallOptions`
   - Validate model parameters
   - Generate warnings for unsupported settings

2. **Convert prompt to provider format**
   - Transform `LanguageModelV1Prompt` to provider-specific format
   - Handle different message roles and content types
   - Process multimodal content (images, files)

3. **Call the underlying API**
   - Use provider SDK/API with converted prompt
   - Handle abort signals
   - Manage authentication and errors

4. **Process the response**
   - Extract text, tool calls, and other content
   - Calculate token usage
   - Determine finish reason
   - For object-json mode, output is already schema-constrained via native `responseJsonSchema`

5. **Return standardized result**
   - Include all required fields (text, usage, finishReason, rawCall)
   - Add optional fields as available (toolCalls, warnings, providerMetadata)
   - Provide debugging information (rawResponse, request)

## Key Considerations

1. **Error Handling**: Use `@ai-sdk/provider` error types like `APICallError`, `NoSuchModelError`, `LoadAPIKeyError`

2. **Abort Signal**: Properly handle `options.abortSignal` for cancellation

3. **Mode Handling**:
   - `regular`: Standard text generation with optional tools
   - `object-json`: JSON generation mode (uses native `responseJsonSchema` for schema-constrained output)
   - `object-tool`: Tool-based object generation

4. **Warnings**: Generate warnings for unsupported parameters or validation issues

5. **Provider Metadata**: Pass through provider-specific data that doesn't fit standard fields

6. **Raw Data**: Include raw prompt/settings in `rawCall` for debugging and observability

This summary provides the essential types and patterns needed to implement a compliant `doGenerate` method for the Vercel AI SDK Language Model V1 interface.

--- docs/project-structure.md ---
# Project Structure

This document provides an overview of the ai-sdk-provider-gemini-cli codebase organization.

```
ai-sdk-provider-gemini-cli/
├── src/                                      # Source code
│   ├── index.ts                              # Main exports
│   ├── gemini-provider.ts                    # Provider factory function
│   ├── gemini-language-model.ts              # Core LanguageModelV2 implementation (v5)
│   ├── client.ts                             # Gemini CLI Core client initialization
│   ├── message-mapper.ts                     # Maps AI SDK messages to Gemini format
│   ├── tool-mapper.ts                        # Maps AI SDK tools to Gemini format
│   ├── error.ts                              # Error handling and mapping
│   ├── validation.ts                         # Input validation utilities
│   ├── types.ts                              # TypeScript type definitions
│   └── __tests__/                            # Unit tests
│       ├── client.test.ts                    # Client initialization tests
│       ├── error.test.ts                     # Error handling tests
│       ├── gemini-language-model.test.ts     # Language model tests
│       ├── gemini-provider.test.ts           # Provider creation tests
│       ├── index.test.ts                     # Main exports tests
│       ├── message-mapper.test.ts            # Message mapping tests
│       ├── tool-mapper.test.ts               # Tool mapping tests
│       └── validation.test.ts                # Validation logic tests
│
├── examples/                                 # Usage examples
│   ├── README.md                             # Examples documentation
│   ├── check-auth.mjs                        # Authentication verification
│   ├── basic-usage.mjs                       # Simple text generation
│   ├── streaming.mjs                         # Streaming responses
│   ├── conversation-history.mjs              # Multi-turn conversations
│   ├── system-messages.mjs                   # System prompts
│   ├── custom-config.mjs                     # Provider configuration
│   ├── error-handling.mjs                    # Error handling patterns
│   ├── long-running-tasks.mjs                # Timeout management
│   ├── integration-test.mjs                  # Comprehensive testing
│   └── generate-object-*.mjs                 # Object generation examples
│       ├── basic.mjs                         # Basic object generation
│       ├── nested.mjs                        # Nested structures
│       ├── constraints.mjs                   # Validation constraints
│       └── advanced.mjs                      # Complex real-world examples
│
├── docs/                                     # Technical documentation
│   ├── README.md                             # Documentation index
│   ├── project-structure.md                  # This file
│   ├── gemini-cli-auth-options.md            # Authentication details
│   ├── language-model-v2-implementation.md   # AI SDK v5 interface
│   ├── tool-schema-mapping.md                # Tool schema conversion
│   ├── zod-to-gemini-mapping.md              # Zod to Gemini mapping
│   └── ai-sdk-v5/                            # v5 specific docs
│       ├── BREAKING_CHANGES.md               # Migration guide from v4
│       ├── DEVELOPMENT_STATUS.md             # Current development status
│       ├── GUIDE.md                          # Comprehensive usage guide
│       └── TROUBLESHOOTING.md                # Common issues and solutions
│
├── dist/                                     # Build output (generated)
│   ├── index.js                              # CommonJS bundle
│   ├── index.mjs                             # ES Module bundle
│   ├── index.d.ts                            # TypeScript declarations
│   └── *.map                                 # Source maps
│
├── Configuration Files
│   ├── package.json                          # Project metadata and scripts
│   ├── tsconfig.json                         # TypeScript configuration
│   ├── tsconfig.build.json                   # Build-specific TS config
│   ├── tsup.config.ts                        # Build tool configuration
│   ├── vitest.config.ts                      # Test runner configuration
│   ├── eslint.config.js                      # ESLint configuration (flat config)
│   ├── .gitignore                            # Git ignore patterns
│   ├── .npmignore                            # NPM publish ignore patterns
│   └── .prettierrc                           # Code formatter configuration
│
└── Root Files
    ├── README.md                             # Main project documentation
    ├── CONTRIBUTING.md                       # Contributing guidelines
    └── LICENSE                               # MIT license

```

## Key Components

### Core Implementation (`src/`)

- **Provider Entry Points**
  - `index.ts` - Exports all public APIs
  - `gemini-provider.ts` - Factory function for creating providers

- **Language Model**
  - `gemini-language-model.ts` - Implements Vercel AI SDK's LanguageModelV2 interface for v5
  - Handles both streaming and non-streaming generation
  - Manages authentication and client initialization
  - Supports abort signals (with limitations)

- **Message & Tool Processing**
  - `message-mapper.ts` - Converts AI SDK message format to Gemini format
  - `tool-mapper.ts` - Converts function tools from Zod/JSON Schema to Gemini

- **Utilities**
  - `client.ts` - Initializes Gemini CLI Core with proper auth
  - `error.ts` - Maps Gemini errors to AI SDK error types
  - `validation.ts` - Validates model IDs and configurations
  - `logger.ts` - Logging utilities with verbose mode support

### Examples (`examples/`)

Organized by complexity and use case:
- **Getting Started**: Authentication, basic usage, streaming
- **Advanced Features**: Object generation, system messages, error handling
- **Testing**: Integration tests covering all features

### Documentation (`docs/`)

Technical documentation covering:
- Authentication options and setup
- Vercel AI SDK interface implementation
- Schema mapping and conversion details
- This project structure guide

## Development Workflow

1. **Source Code**: All TypeScript source in `src/`
2. **Build Output**: Generated in `dist/` via `npm run build`
3. **Examples**: Runnable examples in `examples/`
4. **Testing**: Run examples as integration tests

## Key Design Decisions

- **Minimal Dependencies**: Only essential packages included
- **Direct Integration**: Uses Gemini CLI Core directly without abstraction layers
- **Type Safety**: Full TypeScript support with comprehensive types
- **AI SDK Compatibility**: Implements standard LanguageModelV2 interface for v5
- **OAuth First**: Designed for OAuth authentication via Gemini CLI

## Test Coverage

The project includes comprehensive unit tests covering:
- Provider creation and configuration
- Message and tool mapping
- Error handling and validation
- Native JSON schema structured output
- Client initialization
- All core functionality

Current test coverage: ~98%

--- docs/tool-schema-mapping.md ---
# Tool Schema Mapping: Vercel AI SDK to Gemini

## Overview

This document outlines the mapping between Vercel AI SDK's Zod-based tool schemas and Google Gemini's FunctionDeclaration format.

**Note**: This mapping applies to both AI SDK v4 and v5. The tool schema format remains consistent across versions.

## Vercel AI SDK Tool Structure

```typescript
// Note: In v5, this is LanguageModelV2FunctionTool
// but the structure remains the same
interface LanguageModelV1FunctionTool {
  type: 'function';
  name: string;
  description?: string;
  parameters: JSONSchema7;
}
```

The AI SDK uses `zodSchema()` utility to convert Zod schemas to JSON Schema 7 format.

## Gemini FunctionDeclaration Format

```typescript
interface FunctionDeclaration {
  name: string;
  description?: string;
  // Option 1: Native Gemini Schema format
  parameters?: Schema;
  // Option 2: Standard JSON Schema (alternative)
  parametersJsonSchema?: object;
}

interface Schema {
  type: 'STRING' | 'NUMBER' | 'INTEGER' | 'BOOLEAN' | 'ARRAY' | 'OBJECT';
  description?: string;
  nullable?: boolean;
  enum?: string[];
  items?: Schema;  // For arrays
  properties?: { [key: string]: Schema };  // For objects
  required?: string[];  // For objects
  // Constraints (note: some use string type)
  minLength?: string;
  maxLength?: string;
  pattern?: string;
  minimum?: number;
  maximum?: number;
  minItems?: string;
  maxItems?: string;
}
```

## Type Mapping Table

### Basic Types

| JSON Schema Type | Gemini Schema Type | Notes |
|-----------------|-------------------|-------|
| `string` | `STRING` | |
| `number` | `NUMBER` | |
| `integer` | `INTEGER` | |
| `boolean` | `BOOLEAN` | |
| `array` | `ARRAY` | Requires `items` |
| `object` | `OBJECT` | Requires `properties` |

### Zod to JSON Schema to Gemini

| Zod Type | JSON Schema | Gemini Schema |
|----------|-------------|---------------|
| `z.string()` | `{type: 'string'}` | `{type: 'STRING'}` |
| `z.number()` | `{type: 'number'}` | `{type: 'NUMBER'}` |
| `z.boolean()` | `{type: 'boolean'}` | `{type: 'BOOLEAN'}` |
| `z.array(T)` | `{type: 'array', items: T}` | `{type: 'ARRAY', items: T}` |
| `z.object({...})` | `{type: 'object', properties: {...}}` | `{type: 'OBJECT', properties: {...}}` |
| `z.enum([...])` | `{enum: [...]}` | `{enum: [...]}` |
| `z.optional(T)` | `T` (not in required) | `T` (not in required) |
| `z.nullable(T)` | `{type: [T, 'null']}` | `{...T, nullable: true}` |

## Special Conversions

### Nullable Types
JSON Schema: `{type: ['string', 'null']}`
Gemini: `{type: 'STRING', nullable: true}`

### Const Values
JSON Schema: `{const: 'value'}`
Gemini: `{enum: ['value']}`

### Numeric Constraints
Note: Some Gemini constraints use string type:
- `minLength`, `maxLength`: string representation of number
- `minItems`, `maxItems`: string representation of number
- `minimum`, `maximum`: number type

### Union Types (anyOf)
Both formats support `anyOf` for union types.

## Implementation Approach

### Current Implementation: Convert to Native Gemini Schema
```typescript
function mapToolsToGeminiFormat(tools: LanguageModelV1FunctionTool[]): Tool[] {
  const functionDeclarations: FunctionDeclaration[] = [];

  for (const tool of tools) {
    functionDeclarations.push({
      name: tool.name,
      description: tool.description,
      parameters: convertToolParameters(tool.parameters),
    });
  }

  return [{ functionDeclarations }];
}
```

The implementation uses the native Gemini Schema format (`parameters` field) rather than `parametersJsonSchema`. This ensures maximum compatibility with the Gemini CLI Core library.

## Unsupported Features

1. **References ($ref)**: Gemini doesn't support JSON Schema references
2. **allOf**: Not directly supported, needs manual merging
3. **Tuple arrays**: Not supported in Gemini
4. **Complex validation**: Some JSON Schema validation rules have no Gemini equivalent

## Testing Considerations

1. Test with simple types (string, number, boolean)
2. Test with nested objects and arrays
3. Test with optional and nullable fields
4. Test with enums and const values
5. Test with complex real-world schemas
6. Verify constraint conversion (especially string vs number types)

--- docs/zod-to-gemini-mapping.md ---
# Zod to Gemini Function Declaration Mapping

## Overview

This document provides a comprehensive mapping between Vercel AI SDK's use of Zod schemas for tool definitions and Google Gemini's FunctionDeclaration format requirements.

**Compatibility Note**: This mapping applies to both AI SDK v4 and v5. While v5 uses `LanguageModelV2` interfaces, the Zod schema conversion process remains the same.

## Core Type Definitions

### Vercel AI SDK Tool Structure

```typescript
// From @ai-sdk/provider
// Note: In v5 this is LanguageModelV2FunctionTool
interface LanguageModelV1FunctionTool {
  type: 'function';
  name: string;
  description?: string;
  parameters: JSONSchema7;  // JSON Schema format
}

// From @ai-sdk/ai-core
interface Tool<PARAMETERS extends ToolParameters = any, RESULT = any> {
  parameters: PARAMETERS;  // Can be z.ZodTypeAny or Schema<any>
  description?: string;
  execute?: (args: inferParameters<PARAMETERS>, options: ToolExecutionOptions) => PromiseLike<RESULT>;
}
```

### Google Gemini FunctionDeclaration

```typescript
// From @google/genai
interface FunctionDeclaration {
  name: string;
  description?: string;
  parameters?: Schema;  // Gemini's custom Schema format
  parametersJsonSchema?: unknown;  // Alternative: standard JSON Schema
  behavior?: Behavior;
}

interface Schema {
  type?: Type;  // 'TYPE_UNSPECIFIED' | 'STRING' | 'NUMBER' | 'INTEGER' | 'BOOLEAN' | 'ARRAY' | 'OBJECT'
  format?: string;
  description?: string;
  nullable?: boolean;
  enum?: string[];
  items?: Schema;  // For arrays
  properties?: { [key: string]: Schema };  // For objects
  required?: string[];  // For objects
  anyOf?: Schema[];
  default?: unknown;
  example?: unknown;
  
  // String constraints
  maxLength?: string;
  minLength?: string;
  pattern?: string;
  
  // Number constraints
  minimum?: number;
  maximum?: number;
  exclusiveMinimum?: boolean;
  exclusiveMaximum?: boolean;
  
  // Array constraints
  minItems?: string;
  maxItems?: string;
  uniqueItems?: boolean;
  
  // Object constraints
  minProperties?: string;
  maxProperties?: string;
  additionalProperties?: boolean;
}
```

## Conversion Strategy

### 1. Tool Definition Conversion

```typescript
function convertVercelToolToGemini(tool: LanguageModelV1FunctionTool): FunctionDeclaration {
  return {
    name: tool.name,
    description: tool.description,
    parameters: convertJSONSchemaToGeminiSchema(tool.parameters)
  };
}
```

### 2. Zod to JSON Schema to Gemini Schema

The conversion happens in two steps:
1. **Zod → JSON Schema**: Using `zod-to-json-schema` library (already done by Vercel AI SDK)
2. **JSON Schema → Gemini Schema**: Custom conversion logic

### 3. Type Mapping Table

| Zod Type | JSON Schema Type | Gemini Schema Type | Notes |
|----------|------------------|-------------------|-------|
| `z.string()` | `{ type: 'string' }` | `{ type: 'STRING' }` | |
| `z.number()` | `{ type: 'number' }` | `{ type: 'NUMBER' }` | |
| `z.boolean()` | `{ type: 'boolean' }` | `{ type: 'BOOLEAN' }` | |
| `z.literal()` | `{ const: value }` | `{ enum: [value] }` | Single enum value |
| `z.enum()` | `{ enum: [...] }` | `{ type: 'STRING', enum: [...] }` | |
| `z.array()` | `{ type: 'array', items: {...} }` | `{ type: 'ARRAY', items: {...} }` | |
| `z.object()` | `{ type: 'object', properties: {...} }` | `{ type: 'OBJECT', properties: {...} }` | |
| `z.union()` | `{ anyOf: [...] }` | `{ anyOf: [...] }` | |
| `z.optional()` | `{ type: [..., 'null'] }` | `{ nullable: true }` | |
| `z.nullable()` | `{ type: [..., 'null'] }` | `{ nullable: true }` | |
| `z.record()` | `{ type: 'object', additionalProperties: {...} }` | `{ type: 'OBJECT', additionalProperties: true }` | |
| `z.tuple()` | `{ type: 'array', items: [...] }` | Not directly supported | Convert to array with items schema |
| `z.intersection()` | `{ allOf: [...] }` | Not directly supported | Merge properties |
| `z.lazy()` | `{ $ref: '...' }` | Not supported | Requires schema flattening |

### 4. Format Mapping

| Zod/JSON Schema Format | Gemini Format | Notes |
|----------------------|---------------|-------|
| `email` | `email` | |
| `url` | `uri` | |
| `uuid` | `uuid` | |
| `date-time` | `date-time` | |
| `date` | `date` | |
| `time` | `time` | |
| `ipv4` | `ipv4` | |
| `ipv6` | `ipv6` | |
| `hostname` | Not supported | Use string |
| `json-pointer` | Not supported | Use string |
| `regex` | Use `pattern` property | |

### 5. Constraint Mapping

#### String Constraints
```typescript
// Zod
z.string().min(5).max(100).regex(/^[A-Z]/)

// JSON Schema
{
  type: 'string',
  minLength: 5,
  maxLength: 100,
  pattern: '^[A-Z]'
}

// Gemini Schema
{
  type: 'STRING',
  minLength: '5',  // Note: string type
  maxLength: '100', // Note: string type
  pattern: '^[A-Z]'
}
```

#### Number Constraints
```typescript
// Zod
z.number().min(0).max(100).int()

// JSON Schema
{
  type: 'integer',
  minimum: 0,
  maximum: 100
}

// Gemini Schema
{
  type: 'INTEGER',
  minimum: 0,
  maximum: 100
}
```

#### Array Constraints
```typescript
// Zod
z.array(z.string()).min(1).max(10)

// JSON Schema
{
  type: 'array',
  items: { type: 'string' },
  minItems: 1,
  maxItems: 10
}

// Gemini Schema
{
  type: 'ARRAY',
  items: { type: 'STRING' },
  minItems: '1',  // Note: string type
  maxItems: '10'  // Note: string type
}
```

## Implementation Guidelines

### 1. Conversion Function Structure

```typescript
function convertJSONSchemaToGeminiSchema(jsonSchema: JSONSchema7): Schema {
  // Handle boolean schemas
  if (typeof jsonSchema === 'boolean') {
    return { type: 'BOOLEAN' };
  }

  const geminiSchema: Schema = {};

  // Convert type
  if (jsonSchema.type) {
    geminiSchema.type = mapJSONSchemaTypeToGemini(jsonSchema.type);
  }

  // Convert constraints based on type
  if (geminiSchema.type === 'STRING') {
    if (jsonSchema.minLength !== undefined) {
      geminiSchema.minLength = String(jsonSchema.minLength);
    }
    if (jsonSchema.maxLength !== undefined) {
      geminiSchema.maxLength = String(jsonSchema.maxLength);
    }
    if (jsonSchema.pattern) {
      geminiSchema.pattern = jsonSchema.pattern;
    }
  }

  // Handle objects
  if (geminiSchema.type === 'OBJECT' && jsonSchema.properties) {
    geminiSchema.properties = {};
    for (const [key, value] of Object.entries(jsonSchema.properties)) {
      geminiSchema.properties[key] = convertJSONSchemaToGeminiSchema(value);
    }
    if (jsonSchema.required) {
      geminiSchema.required = jsonSchema.required;
    }
  }

  // Handle arrays
  if (geminiSchema.type === 'ARRAY' && jsonSchema.items) {
    geminiSchema.items = convertJSONSchemaToGeminiSchema(jsonSchema.items);
  }

  // Copy common properties
  if (jsonSchema.description) {
    geminiSchema.description = jsonSchema.description;
  }
  if (jsonSchema.enum) {
    geminiSchema.enum = jsonSchema.enum.map(String);
  }
  if (jsonSchema.default !== undefined) {
    geminiSchema.default = jsonSchema.default;
  }

  return geminiSchema;
}
```

### 2. Type Mapping Helper

```typescript
function mapJSONSchemaTypeToGemini(type: JSONSchema7TypeName | JSONSchema7TypeName[]): string {
  if (Array.isArray(type)) {
    // Handle nullable types
    const nonNullTypes = type.filter(t => t !== 'null');
    if (nonNullTypes.length === 1) {
      return mapSingleType(nonNullTypes[0]);
    }
    // Multiple non-null types not directly supported
    return 'TYPE_UNSPECIFIED';
  }
  return mapSingleType(type);
}

function mapSingleType(type: JSONSchema7TypeName): string {
  switch (type) {
    case 'string': return 'STRING';
    case 'number': return 'NUMBER';
    case 'integer': return 'INTEGER';
    case 'boolean': return 'BOOLEAN';
    case 'array': return 'ARRAY';
    case 'object': return 'OBJECT';
    case 'null': return 'TYPE_UNSPECIFIED';
    default: return 'TYPE_UNSPECIFIED';
  }
}
```

### 3. Special Cases Handling

#### Nullable Types
```typescript
// JSON Schema: { type: ['string', 'null'] }
// Gemini: { type: 'STRING', nullable: true }
```

#### Union Types (anyOf)
```typescript
// JSON Schema: { anyOf: [{ type: 'string' }, { type: 'number' }] }
// Gemini: { anyOf: [{ type: 'STRING' }, { type: 'NUMBER' }] }
```

#### Empty Objects
```typescript
// JSON Schema: { type: 'object', properties: {} }
// Gemini: omit parameters entirely or use parametersJsonSchema
```

## Alternative Approach: Using parametersJsonSchema

Gemini also supports standard JSON Schema through the `parametersJsonSchema` field:

```typescript
function convertVercelToolToGeminiWithJsonSchema(tool: LanguageModelV1FunctionTool): FunctionDeclaration {
  return {
    name: tool.name,
    description: tool.description,
    parametersJsonSchema: tool.parameters  // Use JSON Schema directly
  };
}
```

This approach may be simpler but requires verifying that Gemini CLI Core supports this field.

## Testing Considerations

1. **Type Coverage**: Test all Zod primitive types and their combinations
2. **Constraint Validation**: Ensure numeric constraints are properly converted to strings where required
3. **Nested Structures**: Test deeply nested objects and arrays
4. **Edge Cases**: Empty objects, null values, undefined properties
5. **Schema References**: Handle or reject recursive schemas appropriately

## Recommendations

1. **Primary Approach**: Convert to Gemini's native Schema format for maximum compatibility
2. **Fallback**: Use `parametersJsonSchema` if supported by Gemini CLI Core
3. **Validation**: Implement runtime validation to ensure converted schemas are valid
4. **Documentation**: Document any limitations or unsupported features
5. **Error Handling**: Provide clear error messages for unsupported schema patterns

--- docs/ai-sdk-v5/BREAKING_CHANGES.md ---
# Breaking Changes: AI SDK v5

This document outlines the breaking changes when migrating from AI SDK v4 to v5 for the Gemini CLI provider.

## Overview

The Vercel AI SDK v5 introduces significant architectural changes that affect how providers are implemented and used. This provider has been updated to be fully compatible with v5.

## Key Breaking Changes

### 1. Response Format Changes

**v4 Response:**
```typescript
const { text, usage } = await generateText({
  model: gemini('gemini-2.5-pro'),
  prompt: 'Hello',
});
```

**v5 Response:**
```typescript
const result = await generateText({
  model: gemini('gemini-2.5-pro'),
  prompt: 'Hello',
});

// Access properties differently:
console.log(result.text);           // The generated text
console.log(result.usage);          // Token usage info
console.log(result.content[0].text); // Alternative access
```

### 2. Parameter Name Changes

Several parameter names have been updated to align with v5 conventions:

| v4 Parameter | v5 Parameter | Notes |
|--------------|-------------------|-------|
| `maxTokens` | `maxOutputTokens` | Maximum tokens to generate |
| `stopWords` | `stopSequences` | Sequences that stop generation |

### 3. Streaming API Changes

**v4 Streaming:**
```typescript
const { textStream } = await streamText({
  model: gemini('gemini-2.5-pro'),
  prompt: 'Tell me a story',
});

for await (const chunk of textStream) {
  process.stdout.write(chunk);
}
```

**v5 Streaming:**
```typescript
const result = await streamText({
  model: gemini('gemini-2.5-pro'),
  prompt: 'Tell me a story',
});

// Now returns a promise with stream properties
for await (const chunk of result.textStream) {
  process.stdout.write(chunk);
}

// Can also access the full text after streaming
const fullText = await result.text;
```

### 4. Token Usage Property Names

Token usage reporting has been standardized:

**v4:**
```typescript
{
  promptTokens: 10,
  completionTokens: 50,
  totalTokens: 60
}
```

**v5:**
```typescript
{
  inputTokens: 10,
  outputTokens: 50,
  totalTokens: 60
}
```

### 5. Message Format Requirements

v5 enforces stricter message formats:

```typescript
// Messages must have proper role types
messages: [
  { role: 'user', content: 'Hello' },
  { role: 'assistant', content: 'Hi there!' },
  { role: 'user', content: 'How are you?' }
]
```

### 6. Provider Interface Changes

The provider now extends `ProviderV2` and implements `LanguageModelV2`:

```typescript
// Provider extends ProviderV2
class GeminiProvider extends ProviderV2 {
  // Returns LanguageModelV2 instances
}
```

### 7. Error Handling

Error handling has been improved with better error types and messages:

```typescript
try {
  const result = await generateText({
    model: gemini('gemini-2.5-pro'),
    prompt: 'Hello',
  });
} catch (error) {
  // Errors now have consistent structure
  if (error.name === 'AbortError') {
    // Handle cancellation
  }
}
```

### 8. Object Generation

The `generateObject` function now has stricter schema validation:

```typescript
// Schema validation errors now show as:
// "No object generated: could not parse the response"
// This actually means validation failed, not parsing
```

## Migration Guide

### Step 1: Update Dependencies

```bash
npm install ai-sdk-provider-gemini-cli@beta ai@beta
```

### Step 2: Update Import Statements

No changes needed - imports remain the same:

```typescript
import { generateText, streamText } from 'ai';
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';
```

### Step 3: Update Response Handling

Replace all instances of destructured responses:

```typescript
// Old
const { text, usage } = await generateText(...);

// New
const result = await generateText(...);
const text = result.text;
const usage = result.usage;
```

### Step 4: Update Parameter Names

Search and replace parameter names:
- `maxTokens` → `maxOutputTokens`
- `stopWords` → `stopSequences`

### Step 5: Update Token Usage Access

Update any code that accesses token usage:

```typescript
// Old
console.log(usage.promptTokens);
console.log(usage.completionTokens);

// New
console.log(usage.inputTokens);
console.log(usage.outputTokens);
```

### Step 6: Test Thoroughly

Run all tests and examples to ensure compatibility:

```bash
npm run build
npm run example:test
```

## Known Issues

1. **maxOutputTokens with gemini-2.5-pro**: Setting `maxOutputTokens` can cause empty responses with gemini-2.5-pro. Consider omitting this parameter or using gemini-2.5-flash.

2. **Abort Signal Limitation**: The underlying gemini-cli-core doesn't support request cancellation. Abort signals work from the SDK perspective but requests continue in the background.

3. **Schema Validation Messages**: When using `generateObject`, validation failures show misleading "could not parse" errors even though JSON parsing succeeded.

## Need Help?

- Check the [examples](../../examples/) directory for v5 usage patterns
- Review the [GUIDE.md](./GUIDE.md) for detailed usage instructions
- See [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) for common issues

## Links discovered
- [examples](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/../../examples/)
- [GUIDE.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/./GUIDE.md)
- [TROUBLESHOOTING.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/./TROUBLESHOOTING.md)

--- docs/ai-sdk-v5/DEVELOPMENT_STATUS.md ---
# Development Status for AI SDK v5

## Overview

This document tracks the development status of the Gemini CLI Provider for Vercel AI SDK v5 compatibility.

## Current Status: ✅ COMPLETE

The provider has been fully migrated to support AI SDK v5.

## Completed Features

### Core Functionality
- ✅ **Provider Interface**: Extends `ProviderV2` correctly
- ✅ **Language Model**: Implements `LanguageModelV2` interface
- ✅ **Text Generation**: Full `generateText` support with v5 response format
- ✅ **Streaming**: Complete `streamText` implementation with promise-based API
- ✅ **Object Generation**: `generateObject` with Zod schema validation
- ✅ **System Messages**: Proper system instruction support
- ✅ **Conversation History**: Multi-turn conversation support
- ✅ **Multimodal**: Base64 image support (URL images not supported by design)

### Authentication
- ✅ **OAuth Personal**: Default authentication via Gemini CLI
- ✅ **API Key**: Both `api-key` and `gemini-api-key` auth types
- ✅ **Credential Management**: Uses `~/.gemini/oauth_creds.json`

### Models
- ✅ **gemini-3-pro-preview**: Full support (Preview)
- ✅ **gemini-2.5-pro**: Full support (Previous generation)
- ✅ **gemini-2.5-flash**: Full support for faster responses

### Error Handling
- ✅ **Error Mapping**: Proper error types for v5
- ✅ **Abort Signals**: Correct AbortError handling (with limitations)
- ✅ **Validation Errors**: Clear error messages for schema failures

### Documentation
- ✅ **Breaking Changes Guide**: Complete migration guide from v4
- ✅ **Usage Guide**: Comprehensive v5 patterns and examples
- ✅ **Troubleshooting**: Common issues and solutions documented
- ✅ **API Documentation**: All interfaces documented

### Examples
- ✅ All 14 example files updated and tested with v5
- ✅ Examples use gemini-2.5-pro for consistency
- ✅ Clear documentation of patterns and best practices

## Known Limitations

### 1. Abort Signal Support
- **Status**: Partial
- **Issue**: The underlying `gemini-cli-core` doesn't support request cancellation
- **Impact**: Abort signals work from SDK perspective but HTTP requests continue in background
- **Workaround**: None - this is a limitation of the underlying library

### 2. maxOutputTokens with gemini-2.5-pro
- **Status**: Known Issue
- **Issue**: Setting `maxOutputTokens` can cause empty responses
- **Impact**: Users may get unexpected empty results
- **Workaround**: Omit the parameter or use gemini-2.5-flash

### 3. Image URL Support
- **Status**: Not Supported
- **Issue**: Only base64-encoded images are supported
- **Impact**: Users must convert images to base64
- **Workaround**: Read images as buffers and encode to base64

### 4. Unsupported Parameters
- **frequencyPenalty**: Not supported by Gemini
- **presencePenalty**: Not supported by Gemini
- **seed**: Not supported by Gemini
- **responseFormat**: Partially supported (JSON mode only)

## Testing Status

### Unit Tests
- ✅ All tests updated for v5 compatibility
- ✅ 98.85% test coverage achieved
- ✅ All tests passing

### Integration Tests
- ✅ All examples run successfully
- ✅ Authentication verified
- ✅ Model responses validated

### Manual Testing
- ✅ Basic text generation
- ✅ Streaming responses
- ✅ Object generation
- ✅ System messages
- ✅ Conversation history
- ✅ Error scenarios
- ✅ Timeout/abort handling

## Migration Checklist

- [x] Update dependencies to v5 versions
- [x] Implement ProviderV2 interface
- [x] Implement LanguageModelV2 interface
- [x] Update message format handling
- [x] Update streaming implementation
- [x] Update token usage property names
- [x] Update parameter names (maxTokens → maxOutputTokens)
- [x] Update error handling
- [x] Update all examples
- [x] Update all documentation
- [x] Run comprehensive tests

## Version Information

- **Provider Version**: 1.0.0-beta.x
- **AI SDK Version**: 5.0.0-beta.26+
- **AI SDK Provider**: 2.0.0-beta.1+
- **Node.js**: ≥20 required

## Future Considerations

1. **Request Cancellation**: If `gemini-cli-core` adds abort support, update provider
2. **New Models**: Add support for new Gemini models as they become available
3. **Additional Features**: Monitor AI SDK v5 for new features to support
4. **Performance**: Consider optimization opportunities for streaming

## Support

For issues or questions:
- Review [TROUBLESHOOTING.md](./TROUBLESHOOTING.md)
- Check [examples](../../examples/) for patterns
- File issues on GitHub repository

## Links discovered
- [TROUBLESHOOTING.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/./TROUBLESHOOTING.md)
- [examples](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/../../examples/)

--- docs/ai-sdk-v5/GUIDE.md ---
# Gemini CLI Provider for AI SDK v5 Guide

This guide covers how to use the Gemini CLI Provider with Vercel AI SDK v5.

## Table of Contents

- [Installation](#installation)
- [Authentication](#authentication)
- [Basic Usage](#basic-usage)
- [Streaming](#streaming)
- [Conversation History](#conversation-history)
- [System Messages](#system-messages)
- [Structured Output](#structured-output)
- [Error Handling](#error-handling)
- [Advanced Features](#advanced-features)
- [Best Practices](#best-practices)

## Installation

```bash
# Install the beta versions
npm install ai-sdk-provider-gemini-cli@beta ai@beta

# Install and set up Gemini CLI
npm install -g @google/gemini-cli
gemini  # Follow authentication setup
```

## Authentication

### OAuth Authentication (Recommended)

```typescript
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';

const gemini = createGeminiProvider({
  authType: 'oauth-personal',
});
```

### API Key Authentication

```typescript
const gemini = createGeminiProvider({
  authType: 'api-key',
  apiKey: process.env.GEMINI_API_KEY,
});
```

## Basic Usage

### Text Generation

```typescript
import { generateText } from 'ai';
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';

const gemini = createGeminiProvider({
  authType: 'oauth-personal',
});

async function generate() {
  const result = await generateText({
    model: gemini('gemini-2.5-pro'),
    prompt: 'Write a haiku about coding',
  });

  console.log(result.text);
  console.log(`Tokens used: ${result.usage?.totalTokens}`);
}
```

### Model Configuration

```typescript
const model = gemini('gemini-2.5-pro', {
  temperature: 0.7,        // Creativity (0-2)
  maxOutputTokens: 1000,   // Max tokens to generate
  topP: 0.95,             // Nucleus sampling
  topK: 40,               // Top-k sampling
});
```

## Streaming

### Basic Streaming

```typescript
import { streamText } from 'ai';

async function stream() {
  const result = await streamText({
    model: gemini('gemini-2.5-pro'),
    prompt: 'Tell me a story about a robot',
  });

  // Stream chunks as they arrive
  for await (const chunk of result.textStream) {
    process.stdout.write(chunk);
  }

  // Access full text after streaming
  const fullText = await result.text;
  console.log('\n\nFull text length:', fullText.length);
}
```

### Progress Tracking

```typescript
async function streamWithProgress() {
  const result = await streamText({
    model: gemini('gemini-2.5-pro'),
    prompt: 'Write a detailed article about AI',
  });

  let charCount = 0;
  const startTime = Date.now();

  for await (const chunk of result.textStream) {
    charCount += chunk.length;
    const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
    process.stdout.write(`\r📝 Generated: ${charCount} chars | Time: ${elapsed}s`);
  }

  console.log('\n✅ Complete!');
}
```

## Conversation History

### Multi-turn Conversations

```typescript
async function conversation() {
  const result = await generateText({
    model: gemini('gemini-2.5-pro'),
    messages: [
      { role: 'user', content: 'My name is Alice' },
      { role: 'assistant', content: 'Nice to meet you, Alice! How can I help you today?' },
      { role: 'user', content: 'What is my name?' },
    ],
  });

  console.log(result.text); // Should remember "Alice"
}
```

### Building Conversation Context

```typescript
const messages = [];

function addUserMessage(content: string) {
  messages.push({ role: 'user', content });
}

function addAssistantMessage(content: string) {
  messages.push({ role: 'assistant', content });
}

async function continueConversation(userInput: string) {
  addUserMessage(userInput);
  
  const result = await generateText({
    model: gemini('gemini-2.5-pro'),
    messages,
  });

  addAssistantMessage(result.text);
  return result.text;
}
```

## System Messages

### Setting Model Behavior

```typescript
async function withSystemMessage() {
  const result = await generateText({
    model: gemini('gemini-2.5-pro'),
    system: 'You are a helpful coding assistant. Always include code examples in your responses.',
    prompt: 'How do I read a file in Node.js?',
  });

  console.log(result.text); // Will include code examples
}
```

### Complex System Instructions

```typescript
const system = `You are an expert TypeScript developer.
- Always use modern ES6+ syntax
- Include type annotations
- Follow best practices
- Explain your code clearly`;

const result = await generateText({
  model: gemini('gemini-2.5-pro'),
  system,
  prompt: 'Create a generic cache class',
});
```

## Structured Output

### Basic Object Generation

```typescript
import { generateObject } from 'ai';
import { z } from 'zod';

async function generateProduct() {
  const result = await generateObject({
    model: gemini('gemini-2.5-pro'),
    schema: z.object({
      name: z.string().describe('Product name'),
      price: z.number().describe('Price in USD'),
      inStock: z.boolean().describe('Availability'),
    }),
    prompt: 'Generate a laptop product',
  });

  console.log(result.object);
  // { name: "UltraBook Pro", price: 1299.99, inStock: true }
}
```

### Nested Structures

```typescript
const CompanySchema = z.object({
  name: z.string(),
  founded: z.number(),
  employees: z.array(z.object({
    name: z.string(),
    role: z.string(),
    department: z.string(),
  })),
  metrics: z.object({
    revenue: z.number(),
    growth: z.number(),
  }),
});

const result = await generateObject({
  model: gemini('gemini-2.5-pro'),
  schema: CompanySchema,
  prompt: 'Generate a tech startup company profile',
});
```

### Handling Validation

```typescript
try {
  const result = await generateObject({
    model: gemini('gemini-2.5-pro'),
    schema: z.object({
      description: z.string().max(100), // Strict limit
    }),
    prompt: 'Describe quantum computing',
  });
} catch (error) {
  // Note: Error may say "could not parse" but usually means
  // validation failed (e.g., string too long)
  console.error('Validation failed:', error.message);
}
```

## Error Handling

### Basic Error Handling

```typescript
try {
  const result = await generateText({
    model: gemini('gemini-2.5-pro'),
    prompt: 'Hello',
  });
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Request was cancelled');
  } else if (error.message.includes('quota')) {
    console.log('Rate limit exceeded');
  } else {
    console.error('Unexpected error:', error);
  }
}
```

### Timeout Management

```typescript
async function withTimeout() {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 10000); // 10 seconds

  try {
    const result = await generateText({
      model: gemini('gemini-2.5-pro'),
      prompt: 'Write a detailed analysis',
      abortSignal: controller.signal,
    });
    
    clearTimeout(timeout);
    return result.text;
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Request timed out');
    }
    throw error;
  }
}
```

**Note**: Due to gemini-cli-core limitations, aborted requests continue in the background even though the SDK throws AbortError.

## Advanced Features

### Multimodal Input (Images)

```typescript
import { readFileSync } from 'fs';

async function analyzeImage() {
  const imageBuffer = readFileSync('diagram.png');
  const base64Image = imageBuffer.toString('base64');

  const result = await generateText({
    model: gemini('gemini-2.5-pro'),
    messages: [{
      role: 'user',
      content: [
        { type: 'text', text: 'What is shown in this image?' },
        { type: 'image', data: base64Image },
      ],
    }],
  });

  console.log(result.text);
}
```

### Token Usage Monitoring

```typescript
async function trackUsage() {
  const results = [];
  
  for (const prompt of prompts) {
    const result = await generateText({
      model: gemini('gemini-2.5-pro'),
      prompt,
    });
    
    results.push({
      prompt: prompt.substring(0, 50),
      inputTokens: result.usage?.inputTokens || 0,
      outputTokens: result.usage?.outputTokens || 0,
      totalTokens: result.usage?.totalTokens || 0,
    });
  }
  
  const totalTokens = results.reduce((sum, r) => sum + r.totalTokens, 0);
  console.log('Total tokens used:', totalTokens);
}
```

## Best Practices

### 1. Model Selection

- Use **gemini-3-pro-preview** for enhanced reasoning capabilities
- Use **gemini-2.5-pro** for production workloads requiring the previous generation stable model
- Use **gemini-2.5-flash** for simpler tasks where speed is important

### 2. Prompt Engineering

```typescript
// Be specific and clear
const goodPrompt = `Write a Python function that:
1. Takes a list of integers as input
2. Returns the sum of even numbers
3. Includes type hints
4. Has a docstring`;

// Avoid vague prompts
const badPrompt = 'Write a function';
```

### 3. Error Recovery

```typescript
async function generateWithRetry(prompt: string, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const result = await generateText({
        model: gemini('gemini-2.5-pro'),
        prompt,
      });
      return result;
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      
      // Wait before retry (exponential backoff)
      await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
    }
  }
}
```

### 4. Memory Management

For long conversations, consider truncating message history:

```typescript
function truncateMessages(messages: any[], maxMessages = 20) {
  if (messages.length <= maxMessages) return messages;
  
  // Keep system message (if any) and recent messages
  const systemMsg = messages.find(m => m.role === 'system');
  const recentMessages = messages.slice(-maxMessages);
  
  return systemMsg ? [systemMsg, ...recentMessages] : recentMessages;
}
```

### 5. Streaming Best Practices

```typescript
// Clean up resources on error
async function safeStream() {
  let result;
  
  try {
    result = await streamText({
      model: gemini('gemini-2.5-pro'),
      prompt: 'Tell me a story',
    });
    
    for await (const chunk of result.textStream) {
      process.stdout.write(chunk);
    }
  } catch (error) {
    console.error('Stream error:', error);
    // Ensure stream is properly closed
    if (result?.textStream) {
      result.textStream.return?.();
    }
  }
}
```

## Next Steps

- Explore the [examples](../../examples/) directory for more patterns
- Check [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) for common issues
- Review [BREAKING_CHANGES.md](./BREAKING_CHANGES.md) if migrating from v4

## Links discovered
- [examples](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/../../examples/)
- [TROUBLESHOOTING.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/./TROUBLESHOOTING.md)
- [BREAKING_CHANGES.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/./BREAKING_CHANGES.md)

--- examples/README.md ---
# Gemini CLI AI SDK Provider Examples

This directory contains curated examples demonstrating the key features of the Gemini CLI AI SDK Provider. Each example shows how to use Google's Gemini models through the Cloud Code endpoints with the Vercel AI SDK.

## Prerequisites

1. Authenticate with Google Cloud Code:
```bash
# Run the gemini CLI and follow interactive setup
gemini
```

2. Build the provider:
```bash
npm run build
```

3. Verify your setup:
```bash
node examples/check-auth.mjs
```

## Quick Start Examples

### 1. Basic Usage (`basic-usage.mjs`)
**Purpose**: The simplest example - generate text with Gemini and display metadata.
```bash
node examples/basic-usage.mjs
```
**Key concepts**: Text generation, token usage, OAuth authentication

### 2. Streaming (`streaming.mjs`)
**Purpose**: Demonstrate real-time streaming for responsive user experiences.
```bash
node examples/streaming.mjs
```
**Key concepts**: Stream processing, chunk handling, real-time output

### 3. Conversation History (`conversation-history.mjs`)
**Purpose**: Show how to maintain context across multiple messages.
```bash
node examples/conversation-history.mjs
```
**Key concepts**: Message history, context preservation, multi-turn conversations

## Logging Examples

The provider includes a flexible logging system that can be configured for different use cases. These examples demonstrate all logging modes:

### 4. Default Logging (`logging-default.mjs`)

**Purpose**: Understand the default logging behavior (non-verbose mode).

```bash
node examples/logging-default.mjs
```

**Key concepts**: Default behavior, warn/error only, clean output

**What you'll see**: Only warning and error messages appear. Debug and info logs are suppressed for clean output.

### 5. Verbose Logging (`logging-verbose.mjs`)

**Purpose**: Enable detailed logging for development and troubleshooting.

```bash
node examples/logging-verbose.mjs
```

**Key concepts**: Verbose mode, debug/info logs, execution tracing

**What you'll see**: All log levels (debug, info, warn, error) showing detailed provider activity.

### 6. Custom Logger (`logging-custom-logger.mjs`)

**Purpose**: Integrate with external logging systems (Winston, Pino, Datadog, etc.).

```bash
node examples/logging-custom-logger.mjs
```

**Key concepts**: Custom logger implementation, external integration, log formatting

**What you'll see**: Custom-formatted logs with timestamps and prefixes, demonstrating integration patterns.

### 7. Disabled Logging (`logging-disabled.mjs`)

**Purpose**: Completely silent operation with no logs.

```bash
node examples/logging-disabled.mjs
```

**Key concepts**: Silent mode, production deployments, zero output

**What you'll see**: No provider logs at all - completely silent operation.

## Advanced Configuration

### 8. Custom Config (`custom-config.mjs`)
**Purpose**: Demonstrate provider configuration options.
```bash
node examples/custom-config.mjs
```
**Key concepts**: API key auth, OAuth auth, model settings

### 9. System Messages (`system-messages.mjs`)
**Purpose**: Use system prompts to control model behavior.
```bash
node examples/system-messages.mjs
```
**Key concepts**: System instructions, persona control, response formatting

### 10. Long-Running Tasks (`long-running-tasks.mjs`)
**Purpose**: Handle complex tasks with proper timeout management.
```bash
node examples/long-running-tasks.mjs
```
**Key concepts**: AbortSignal, timeout handling, complex reasoning

## Object Generation (Structured Output)

### 11. Object Generation Basic (`generate-object-basic.mjs`)
**Purpose**: Learn structured output generation step-by-step.
```bash
node examples/generate-object-basic.mjs
```
**Key concepts**: Zod schemas, JSON generation, validation

### 12. Nested Structures (`generate-object-nested.mjs`)
**Purpose**: Generate complex hierarchical data structures.
```bash
node examples/generate-object-nested.mjs
```
**Key concepts**: Nested objects, arrays of objects, complex relationships

### 13. Validation Constraints (`generate-object-constraints.mjs`)
**Purpose**: Enforce data quality with validation rules.
```bash
node examples/generate-object-constraints.mjs
```
**Key concepts**: Enums, ranges, patterns, business rules

### 14. Advanced Object Generation (`generate-object-advanced.mjs`)
**Purpose**: Real-world examples of complex object generation.
```bash
node examples/generate-object-advanced.mjs
```
**Key concepts**: Product catalogs, analytics data, form generation

## Testing & Troubleshooting

### 15. Check Authentication (`check-auth.mjs`)
**Purpose**: Verify Google Cloud Code authentication status.
```bash
node examples/check-auth.mjs
```
**Key concepts**: OAuth validation, credential refresh, troubleshooting

### 16. Integration Test (`integration-test.mjs`)
**Purpose**: Comprehensive test suite to verify all features.
```bash
node examples/integration-test.mjs
```
**Key concepts**: Feature verification, error handling, test patterns

### 17. Error Handling (`error-handling.mjs`)
**Purpose**: Demonstrate proper error handling patterns.
```bash
node examples/error-handling.mjs
```
**Key concepts**: Authentication errors, rate limits, retry logic

## Common Patterns

### OAuth Authentication
```javascript
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';

// Uses ~/.gemini/oauth_creds.json automatically
const gemini = createGeminiProvider({
  authType: 'oauth-personal'
});
```

### API Key Authentication
```javascript
const gemini = createGeminiProvider({
  authType: 'api-key',
  apiKey: process.env.GEMINI_API_KEY
});
```

### Message History
```javascript
const messages = [
  { role: 'user', content: 'My name is Alice' },
  { role: 'assistant', content: 'Nice to meet you, Alice!' },
  { role: 'user', content: 'What is my name?' }
];

const { text } = await generateText({
  model: gemini('gemini-3-pro-preview'),
  messages,
});
```

### Custom Timeouts
```javascript
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 60000); // 1 minute

try {
  const { text } = await generateText({
    model: gemini('gemini-3-pro-preview'),
    prompt: 'Complex analysis...',
    abortSignal: controller.signal,
  });
  clearTimeout(timeout);
} catch (error) {
  // Handle timeout
}
```

## Quick Reference

| Example | Primary Use Case | Key Feature |
|---------|-----------------|-------------|
| basic-usage | Getting started | Simple generation |
| streaming | Responsive UIs | Real-time output |
| conversation-history | Chatbots | Context retention |
| logging-default | Default behavior | Warn/error only |
| logging-verbose | Development/debugging | All log levels |
| logging-custom-logger | External integration | Custom logger impl |
| logging-disabled | Silent operation | No logs at all |
| custom-config | Authentication | OAuth vs API key |
| system-messages | Response control | System prompts |
| generate-object-basic | Learning | Structured output |
| generate-object-nested | Complex data | Hierarchical JSON |
| check-auth | Setup | Authentication status |

## Learning Path

1. **Beginners**: `check-auth.mjs` → `basic-usage.mjs` → `streaming.mjs` → `conversation-history.mjs`
2. **Logging**: `logging-default.mjs` → `logging-verbose.mjs` → `logging-custom-logger.mjs` → `logging-disabled.mjs`
3. **Object Generation**: `generate-object-basic.mjs` → `generate-object-nested.mjs` → `generate-object-advanced.mjs`
4. **Advanced**: `system-messages.mjs` → `long-running-tasks.mjs` → `error-handling.mjs`
5. **Testing**: Run `integration-test.mjs` to verify everything works

## Cloud Code Endpoints

This provider uses Google Cloud Code endpoints (https://cloudcode-pa.googleapis.com) through the gemini-cli-core library. The available models include:
- `gemini-3-pro-preview` - Latest next-generation model (Preview) - **Recommended for all examples**
- `gemini-2.5-pro` - Previous generation production-ready model (64K output tokens)
- `gemini-2.5-flash` - Faster, efficient model (64K output tokens)
- And more models as they become available

**Note**: The provider defaults to 64K output tokens to take full advantage of Gemini 2.5's capabilities. You can override this with the `maxTokens` parameter if needed.

## Troubleshooting

If you encounter authentication issues:
1. Run `gemini` and follow setup prompts to authenticate
2. Check `~/.gemini/oauth_creds.json` exists
3. Run `node examples/check-auth.mjs` to verify

If you encounter rate limit errors:
- Add delays between requests if running multiple examples
- Consider reducing the number of concurrent requests
- Check your quota in the Google Cloud Console

For object generation issues:
- Very strict character length constraints (e.g., exactly 60-80 chars) can be challenging
- Consider using ranges or slightly more flexible constraints
- The model may occasionally exceed limits by a few characters
- **Important**: When using `generateObject`, validation failures will throw an error saying "No object generated: could not parse the response"
  - This error message is misleading - the JSON was likely parsed successfully but failed schema validation
  - The actual generated object is available in the error's `text` property
  - For production use with strict schemas, consider using `generateText` with JSON mode for more control over validation

For more details, see the main [README](../README.md).

## Links discovered
- [README](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/examples/../README.md)

--- CHANGELOG.md ---
# Changelog

All notable changes to this project will be documented in this file.

## [1.4.0] - 2025-11-23

### Added

- **Native Structured Output Support**: Implemented native `responseJsonSchema` support for Gemini API
  - Enables `supportsStructuredOutputs = true` for improved AI SDK integration
  - Schema is now passed directly to Gemini API via `responseJsonSchema` in generation config
  - Provides cleaner, more reliable JSON output without post-processing

- **JSON Without Schema Handling**: Graceful downgrade when JSON format is requested without a schema
  - Emits `unsupported-setting` warning to inform users
  - Downgrades to `text/plain` response format (prevents fenced/raw JSON leaking)
  - Aligns with Claude-code provider behavior for cross-provider consistency
  - Works in both streaming and non-streaming modes

### Changed

- **Dependency Updates** (aligned with @google/gemini-cli-core 0.17.1):
  - `@ai-sdk/provider-utils`: 3.0.3 → 3.0.17
  - `@google/gemini-cli-core`: 0.16.0 → 0.17.1
  - `@google/genai`: 1.14.0 → 1.16.0 (aligned with gemini-cli-core)
  - `google-auth-library`: 10.2.1 → ^9.11.0 (aligned with gemini-cli-core)
  - `zod-to-json-schema`: 3.24.6 → 3.25.0

### Removed

- **Prompt-based Schema Injection**: Removed workaround that injected schema instructions into user prompts
  - No longer needed with native `responseJsonSchema` support
- **JSON Extraction Utility**: Removed `extract-json.ts` and related post-processing
  - Gemini now returns clean JSON directly when schema is provided

### Technical Details

- Streaming now outputs JSON chunks directly without accumulation
- Simplified codebase with shared `prepareGenerationConfig` helper for consistent behavior
- Tests updated to use JSON Schema objects instead of Zod schemas (matching what AI SDK passes to providers)
- All 175 tests passing (including new no-schema downgrade tests)

## [1.3.0] - 2025-11-18

### Added

- **Support for Gemini 3**: Added support for `gemini-3-pro-preview` model
- **Dependency Update**: Updated `@google/gemini-cli-core` to `0.16.0` (pinned)
- **Security Updates**: Updated dev dependencies (Vite 6, Vitest 4) to resolve security vulnerabilities

### Changed

- **Node.js Requirement**: Updated engine requirement to `node >= 20` to align with `@google/gemini-cli-core` v0.16.0
- **CI/CD**: Removed Node 18 from CI matrix

### Fixed

- **Async Configuration**: Fixed compatibility with `gemini-cli-core` v0.16.0 async configuration loading
- **Example Compatibility**: Ensured core health-check examples (`check-auth.mjs`, `integration-test.mjs`) use GA models (`gemini-2.5-pro`) for broader compatibility

## [1.2.0] - 2025-10-21

### Added

- **Comprehensive debug logging and verbose mode** - Enhanced logging capabilities for better debugging and troubleshooting
  - Added `debug` and `info` log levels to complement existing `warn` and `error` levels
  - New `verbose` setting to control debug/info logging visibility
  - Detailed execution tracing including request/response flow, token usage, and timing information
  - `createVerboseLogger()` utility that filters debug/info logs based on verbose mode
  - When `verbose: false` (default), only `warn` and `error` messages are logged
  - When `verbose: true`, all log levels including `debug` and `info` are logged
  - Comprehensive test coverage for all logging scenarios and custom logger implementations

### Changed

- **Logger Interface**: Extended the `Logger` interface from 2 methods to 4 methods
  - Added `debug(message: string): void` - for detailed execution tracing (verbose mode only)
  - Added `info(message: string): void` - for general flow information (verbose mode only)
  - Existing `warn(message: string): void` - for warnings (always shown)
  - Existing `error(message: string): void` - for errors (always shown)
- **Settings**: Added optional `logger` and `verbose` settings to model configuration
  - `logger`: `Logger | false | undefined` - custom logger, disabled, or default console
  - `verbose`: `boolean` - enable/disable debug and info logging (default: false)

### Migration for custom logger users

**Who is affected:** Only users with custom `Logger` implementations.

**What changed:** The `Logger` interface now requires 4 methods instead of 2:

```typescript
// Before (v1.1.2 and earlier) - if you had a custom logger
const logger = {
  warn: (msg) => myLogger.warn(msg),
  error: (msg) => myLogger.error(msg),
};

// After (v1.2.0+)
const logger = {
  debug: (msg) => myLogger.debug(msg), // Add this
  info: (msg) => myLogger.info(msg), // Add this
  warn: (msg) => myLogger.warn(msg),
  error: (msg) => myLogger.error(msg),
};
```

**Most users are unaffected:**

- Users without a custom logger (using default `console`) - no changes needed
- Users with `logger: false` - no changes needed
- The default logger automatically handles all log levels

### Example Usage

```typescript
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';

// Enable verbose logging for debugging
const gemini = createGeminiProvider({
  authType: 'gemini-api-key',
  apiKey: process.env.GEMINI_API_KEY,
});

const model = gemini('gemini-2.5-flash', {
  verbose: true, // Enable debug and info logging
});

// Use with custom logger
const modelWithCustomLogger = gemini('gemini-2.5-flash', {
  verbose: true,
  logger: {
    debug: (msg) => console.log(`[DEBUG] ${msg}`),
    info: (msg) => console.log(`[INFO] ${msg}`),
    warn: (msg) => console.warn(`[WARN] ${msg}`),
    error: (msg) => console.error(`[ERROR] ${msg}`),
  },
});
```

## [1.1.2] - 2025-10-01

### Fixed

- **Multimodal Support**: Fixed image handling crash due to AI SDK v5 API change
  - Updated `LanguageModelV2FilePart` property from `contentType` to `mediaType`
  - Resolves critical issue preventing image attachments from working
- **Import Style**: Changed to use `import type` for type-only imports for better tree-shaking

### Added

- **ToolChoice Support**: Complete implementation of AI SDK toolChoice functionality
  - Added `mapGeminiToolConfig()` function to convert AI SDK toolChoice to Gemini format
  - Proper `allowedFunctionNames` mapping when specific tool is forced
  - Support for all toolChoice types: `auto`, `none`, `required`, `tool`
- **Streaming Parity**: Added `toolConfig` to both `doGenerate` and `doStream` methods
- **Test Coverage**: Added 5 comprehensive tests for `mapGeminiToolConfig` covering all toolChoice scenarios

### Changed

- **Dependency Update**: Updated `@google/gemini-cli-core` from 0.1.22 to 0.6.1
  - Fully tested for backward compatibility
  - All 205 tests passing
  - All 12 examples verified working

### Technical Details

- Combines fixes from community PRs #16 (multimodal crash) and #17 (toolChoice support)
- Enhanced with additional refinements, streaming parity, and comprehensive testing
- No breaking changes - fully backward compatible

## [1.1.1] - 2025-08-22

### Fixed

- **Critical OAuth Fix**: Added `isBrowserLaunchSuppressed()` config method to prevent crashes during OAuth authentication (LOGIN_WITH_GOOGLE)
- **Compatibility**: Full compatibility with @google/gemini-cli-core@0.1.22
  - Updated `generateContent` and `generateContentStream` to use UUID for `userPromptId` parameter
  - Added third `sessionId` parameter to `createContentGenerator` call
  - Pinned exact version `0.1.22` to prevent breaking changes from patch updates

### Added

- **Robust Proxy Pattern**: Enhanced config Proxy to handle multiple method patterns
  - Supports `is*` methods (return false by default)
  - Supports `has*` methods (return false by default)
  - Existing `get*` methods with intelligent defaults based on naming
- **Session Management**: Generate and cache stable session ID per provider instance for better telemetry correlation
- **Comprehensive Documentation**: Added `docs/dependency-notes.md` explaining version pinning rationale and Proxy implementation

### Changed

- Improved type consistency for `authType` in config object
- Updated tests to cover OAuth methods and Proxy behavior

## [1.1.0] - 2025-08-18

### Added

- **Zod 4 Compatibility**: Added support for Zod v4 while maintaining backward compatibility with Zod v3
  - Runtime detection automatically uses the appropriate conversion method
  - Zod v3: Uses `zod-to-json-schema` package
  - Zod v4: Uses native `z.toJSONSchema()` function
  - Both versions listed in peerDependencies: `"^3.0.0 || ^4.0.0"`

### Changed

- Moved `zod` from dependencies to devDependencies to allow users to choose their version
- Updated tool mapping to handle different JSON Schema outputs between Zod versions
  - Union types: Arrays in v3 vs `anyOf` in v4

### Technical Details

- Added `convertZodToJsonSchema` function for runtime version detection
- Tests updated to handle both Zod v3 and v4 union type representations
- Maintained full compatibility with existing API

## [1.0.1] - 2025-08-15

### Changed

- **Stable Release**: Vercel AI SDK v5 is now stable (no longer beta)
- Updated all references from "v5-beta" to "v5"
- Package marked as stable release

### Fixed

- Improved documentation clarity for abort signal limitations
- Updated examples to reflect stable v5 API

### Notes

This is the stable release of v1.0.0-beta.1 with Vercel AI SDK v5 now being officially stable. No breaking changes from v1.0.0-beta.1.

## [1.0.0-beta.1] - 2025-07-24

### BREAKING CHANGES

This version is compatible with Vercel AI SDK v5. For v4 compatibility, please use version 0.x.x.

### Changed

- **Provider Interface**: Migrated from `ProviderV1` to `ProviderV2` interface
  - Updated `createGeminiProvider()` to return `ProviderV2` interface
  - Provider now extends ProviderV2 base class

- **Language Model**: Migrated from `LanguageModelV1` to `LanguageModelV2` interface
  - Changed `specificationVersion` from 'v1' to 'v2'
  - Updated response format to use v5 patterns
  - Improved streaming implementation with promise-based responses

- **Message Format**: Updated to v5 message format
  - Messages now use `ModelMessage` types from v5
  - Tool results integrated into message flow
  - System messages properly supported

- **Response Format**: Updated response structure
  - Streaming now returns promise with stream properties
  - Direct access to `result.text` and `result.usage`
  - Improved token usage tracking

- **Parameter Changes**: Updated parameter names
  - `maxTokens` → `maxOutputTokens` in generation options
  - Token usage: `promptTokens` → `inputTokens`, `completionTokens` → `outputTokens`

- **Error Handling**: Enhanced error handling
  - Proper AbortError support for AI SDK retry logic
  - Better error mapping from Gemini errors
  - Safety status mapping for blocked content

### Added

- Comprehensive abort signal support (with documented limitations)
- New documentation structure in `docs/ai-sdk-v5/`
  - BREAKING_CHANGES.md - Migration guide
  - GUIDE.md - Comprehensive usage guide
  - TROUBLESHOOTING.md - Common issues and solutions
  - DEVELOPMENT_STATUS.md - Implementation status

### Fixed

- System message implementation now works correctly
- Error handling for "Failed after 3 attempts" issue
- Stream error simulation in examples
- Progress indicators in long-running tasks example

### Known Issues

- Abort signals work but underlying gemini-cli-core doesn't support request cancellation
- maxOutputTokens may cause empty responses with gemini-2.5-pro
- Schema validation errors show misleading "could not parse" messages

## [0.1.1] - 2025-01-20

### Added

- Compatibility with gemini-cli-core 0.1.12+ breaking changes
- Comprehensive test suite with 98.85% coverage
- GitHub Actions for automated testing

### Fixed

- Authentication type handling for new gemini-cli-core API
- Error messages and types alignment

## [0.1.0] - 2025-01-15

### Added

- Full support for Vercel AI SDK v4
- OAuth authentication via Gemini CLI
- API key authentication support
- Comprehensive examples directory
- Tool/function calling support
- Multimodal support (text and images)
- Streaming responses

### Changed

- Stable API for v4 compatibility
- Improved error handling
- Better TypeScript types

## [0.0.4] - 2025-01-10

### Fixed

- Authentication configuration issues
- Type definition exports

## [0.0.3] - 2025-01-05

### Added

- System message support
- Object generation with Zod schemas
- More comprehensive examples

### Fixed

- Message mapping for complex conversations

## [0.0.2] - 2025-06-28

### BREAKING CHANGES

- Removed pre-configured geminiProvider export
- Users must now use createGeminiProvider() to create provider instances

### Added

- ESLint with modern flat config and TypeScript support
- Vitest test suite with initial tests
- Test coverage reporting (31.69% initial coverage)
- Alpha warning badge to README

### Changed

- Simplified provider structure
- Improved type safety throughout codebase

### Fixed

- TypeScript strict mode compliance issues

## [0.0.1] - 2025-06-25

### Added

- Initial release
- Basic text generation support
- OAuth authentication via Gemini CLI
- Streaming support
- Basic error handling

[1.1.1]: https://github.com/ben-vargas/ai-sdk-provider-gemini-cli/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/ben-vargas/ai-sdk-provider-gemini-cli/compare/v1.0.1...v1.1.0
[1.0.1]: https://github.com/ben-vargas/ai-sdk-provider-gemini-cli/compare/v1.0.0-beta.1...v1.0.1
[1.0.0-beta.1]: https://github.com/ben-vargas/ai-sdk-provider-gemini-cli/compare/v0.1.1...v1.0.0-beta.1
[0.1.1]: https://github.com/ben-vargas/ai-sdk-provider-gemini-cli/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/ben-vargas/ai-sdk-provider-gemini-cli/compare/v0.0.4...v0.1.0
[0.0.4]: https://github.com/ben-vargas/ai-sdk-provider-gemini-cli/compare/v0.0.3...v0.0.4
[0.0.3]: https://github.com/ben-vargas/ai-sdk-provider-gemini-cli/compare/v0.0.2...v0.0.3
[0.0.2]: https://github.com/ben-vargas/ai-sdk-provider-gemini-cli/compare/v0.0.1...v0.0.2
[0.0.1]: https://github.com/ben-vargas/ai-sdk-provider-gemini-cli/releases/tag/v0.0.1


--- CONTRIBUTING.md ---
# Contributing to AI SDK Provider for Gemini CLI

Thank you for your interest in contributing! We welcome contributions from the community.

## Getting Started

1. Fork the repository
2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/ai-sdk-provider-gemini-cli.git`
3. Install dependencies: `npm install`
4. Build the project: `npm run build`
5. Run examples to verify setup: `npm run example:check`

## Development Workflow

1. Create a feature branch: `git checkout -b feature/your-feature-name`
2. Make your changes
3. Run type checking: `npm run type-check`
4. Build the project: `npm run build`
5. Test your changes with examples
6. Commit using conventional commits (e.g., `feat:`, `fix:`, `docs:`)

## Pull Request Process

1. Ensure your code follows the existing style
2. Update documentation as needed
3. Add examples if introducing new features
4. Ensure all examples still work
5. Submit a pull request with a clear description

## Code Style

- TypeScript for all source code
- Follow existing patterns in the codebase
- Use meaningful variable and function names
- Add JSDoc comments for public APIs

## Testing

While we don't have unit tests yet, please:

- Test your changes using the examples
- Add new examples for new features
- Run `npm run example:test` to verify integration

## Reporting Issues

- Use GitHub Issues for bug reports and feature requests
- Provide clear reproduction steps for bugs
- Include relevant error messages and logs

## Questions?

Feel free to open an issue for any questions about contributing.


--- README.md ---
<p align="center">
  <img src="https://img.shields.io/badge/status-stable-00A79E" alt="stable status">
  <a href="https://www.npmjs.com/package/ai-sdk-provider-gemini-cli"><img src="https://img.shields.io/npm/v/ai-sdk-provider-gemini-cli?color=00A79E" alt="npm stable version" /></a>
  <a href="https://www.npmjs.com/package/ai-sdk-provider-gemini-cli"><img src="https://img.shields.io/npm/unpacked-size/ai-sdk-provider-gemini-cli?color=00A79E" alt="install size" /></a>
  <a href="https://www.npmjs.com/package/ai-sdk-provider-gemini-cli"><img src="https://img.shields.io/npm/dy/ai-sdk-provider-gemini-cli.svg?color=00A79E" alt="npm downloads" /></a>
  <a href="https://nodejs.org/en/about/releases/"><img src="https://img.shields.io/badge/node-%3E%3D20-00A79E" alt="Node.js ≥ 20" /></a>
  <a href="https://www.npmjs.com/package/ai-sdk-provider-gemini-cli"><img src="https://img.shields.io/npm/l/ai-sdk-provider-gemini-cli?color=00A79E" alt="License: MIT" /></a>
  <a href="https://github.com/Piebald-AI/awesome-gemini-cli"><img src="https://awesome.re/mentioned-badge.svg" alt="Mentioned in Awesome Gemini CLI" /></a>
</p>

# AI SDK Provider for Gemini CLI

> **Stable Release**: This version is compatible with AI SDK v5. For AI SDK v4 support, use version 0.x.

A community provider for the [Vercel AI SDK](https://sdk.vercel.ai/docs) that enables using Google's Gemini models through the [@google/gemini-cli-core](https://www.npmjs.com/package/@google/gemini-cli-core) library and Google Cloud Code endpoints.

> **Note**: This provider includes robust compatibility measures for @google/gemini-cli-core, protecting against breaking changes in patch versions through intelligent proxy patterns and exact version pinning.

## Version Compatibility

| Provider Version | AI SDK Version | NPM Tag     | Status | Branch                                                                                 |
| ---------------- | -------------- | ----------- | ------ | -------------------------------------------------------------------------------------- |
| 1.x              | v5             | `latest`    | Stable | `main`                                                                                 |
| 0.x              | v4             | `ai-sdk-v4` | Stable | [`ai-sdk-v4`](https://github.com/ben-vargas/ai-sdk-provider-gemini-cli/tree/ai-sdk-v4) |

### Installing the Right Version

**For AI SDK v5 (current, default):**

```bash
npm install ai-sdk-provider-gemini-cli ai
```

**For AI SDK v4 (legacy):**

```bash
npm install ai-sdk-provider-gemini-cli@ai-sdk-v4 ai@^4.3.16
```

## Disclaimer

**This is an unofficial community provider** and is not affiliated with or endorsed by Google or Vercel. By using this provider:

- You understand that your data will be sent to Google's servers through the Gemini CLI Core library
- You agree to comply with [Google's Terms of Service](https://policies.google.com/terms)
- You acknowledge this software is provided "as is" without warranties of any kind

Please ensure you have appropriate permissions and comply with all applicable terms when using this provider.

## Features

- 🚀 Compatible with Vercel AI SDK (v4 and v5)
- ☁️ Uses Google Cloud Code endpoints (https://cloudcode-pa.googleapis.com)
- 🔄 Streaming support for real-time responses
- 🛠️ Tool/function calling capabilities
- 🖼️ Multimodal support (text and base64 images)
- 🔐 OAuth authentication using Gemini CLI credentials
- 📝 TypeScript support with full type safety
- 🎯 Structured object generation with Zod schemas
- 🐛 Comprehensive logging with verbose mode for debugging

## Installation

### 1. Install and set up the Gemini CLI

```bash
npm install -g @google/gemini-cli
gemini  # Follow the interactive authentication setup
```

### 2. Add the provider

```bash
# For AI SDK v5 (current, default)
npm install ai-sdk-provider-gemini-cli ai

# For AI SDK v4 (legacy)
npm install ai-sdk-provider-gemini-cli@ai-sdk-v4 ai@^4.3.16
```

## Quick Start

### AI SDK v5

```typescript
import { generateText } from 'ai';
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';

// Create provider with OAuth authentication
const gemini = createGeminiProvider({
  authType: 'oauth-personal',
});

const result = await generateText({
  model: gemini('gemini-3-pro-preview'),
  prompt: 'Write a haiku about coding',
});

console.log(result.content[0].text);
```

### AI SDK v4

```typescript
import { generateText } from 'ai';
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';

const gemini = createGeminiProvider({
  authType: 'oauth-personal',
});

const { text } = await generateText({
  model: gemini('gemini-3-pro-preview'),
  prompt: 'Write a haiku about coding',
});

console.log(text);
```

## Breaking Changes in v1.x

See [CHANGELOG.md](CHANGELOG.md) for details on migrating from v0.x to v1.x.

Key changes:

- Requires AI SDK v5
- New response format with content arrays
- Updated parameter names (maxTokens → maxOutputTokens)
- New streaming API patterns
- Updated token usage properties

## Documentation

- **[Examples](examples/)** - Comprehensive examples demonstrating all features
- **[API Reference](docs/)** - Technical documentation and implementation details
- **[Authentication Guide](docs/gemini-cli-auth-options.md)** - Detailed authentication options
- **[Migration Guide](CHANGELOG.md)** - v0.x to v1.x migration guide

## Examples

The `examples/` directory contains comprehensive examples demonstrating all features:

### Getting Started

- `check-auth.mjs` - Verify your authentication setup
- `basic-usage.mjs` - Simple text generation examples
- `streaming.mjs` - Real-time streaming responses
- `conversation-history.mjs` - Multi-turn conversations

### Advanced Features

- `generate-object-basic.mjs` - Structured output with Zod schemas
- `generate-object-nested.mjs` - Complex nested data structures
- `generate-object-constraints.mjs` - Data validation and constraints
- `system-messages.mjs` - Control model behavior with system prompts
- `error-handling.mjs` - Robust error handling patterns

### Run Examples

```bash
# First build the project
npm run build

# Check authentication
npm run example:check

# Run basic examples
npm run example:basic

# Run all tests
npm run example:test
```

See the [examples README](examples/README.md) for detailed documentation.

## Authentication

The provider uses OAuth authentication with Google Cloud Code endpoints:

### OAuth Authentication (Recommended)

```typescript
const gemini = createGeminiProvider({
  authType: 'oauth-personal',
});
```

This uses your existing Gemini CLI credentials from `~/.gemini/oauth_creds.json`. To set up authentication:

```bash
# Initial setup - follow interactive prompts
gemini

# Or change auth method inside CLI with slash command
/auth
```

### API Key Authentication

```typescript
// Using AI SDK standard auth type (recommended)
const gemini = createGeminiProvider({
  authType: 'api-key',
  apiKey: process.env.GEMINI_API_KEY,
});

// Alternative: Gemini-specific auth type
const gemini = createGeminiProvider({
  authType: 'gemini-api-key',
  apiKey: process.env.GEMINI_API_KEY,
});
```

Get your API key from [Google AI Studio](https://aistudio.google.com/apikey) and set it as an environment variable:

```bash
export GEMINI_API_KEY="your-api-key-here"
```

## Usage Examples

### Text Generation

\*\*AI SDK v5:

```typescript
import { generateText } from 'ai';
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';

const gemini = createGeminiProvider({
  authType: 'oauth-personal',
});

const result = await generateText({
  model: gemini('gemini-2.5-pro'),
  prompt: 'Explain quantum computing in simple terms',
  maxOutputTokens: 500,
});

console.log(result.content[0].text);
console.log(`Tokens used: ${result.usage?.totalTokens}`);
```

**AI SDK v4:**

```typescript
const { text, usage } = await generateText({
  model: gemini('gemini-2.5-pro'),
  prompt: 'Explain quantum computing in simple terms',
  maxTokens: 500,
});

console.log(text);
console.log(`Tokens used: ${usage?.totalTokens}`);
```

### Streaming Responses

```typescript
import { streamText } from 'ai';
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';

const gemini = createGeminiProvider({
  authType: 'oauth-personal',
});

const result = await streamText({
  model: gemini('gemini-2.5-pro'),
  prompt: 'Write a story about a robot learning to paint',
});

// v5: Access text stream
for await (const chunk of result.textStream) {
  process.stdout.write(chunk);
}

// v4: Same API for streaming
```

### Object Generation (Structured Output)

```typescript
import { generateObject } from 'ai';
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';
import { z } from 'zod';

const gemini = createGeminiProvider({
  authType: 'oauth-personal',
});

const result = await generateObject({
  model: gemini('gemini-2.5-pro'),
  schema: z.object({
    name: z.string().describe('Product name'),
    price: z.number().describe('Price in USD'),
    features: z.array(z.string()).describe('Key features'),
  }),
  prompt: 'Generate a laptop product listing',
});

console.log(result.object);
```

### System Messages

\*\*AI SDK v5:

```typescript
import { generateText } from 'ai';
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';

const gemini = createGeminiProvider({
  authType: 'oauth-personal',
});

const result = await generateText({
  model: gemini('gemini-2.5-pro'),
  system: 'You are a helpful coding assistant. Always include code examples.',
  prompt: 'How do I read a file in Node.js?',
});

console.log(result.content[0].text);
```

**AI SDK v4:**

```typescript
const { text } = await generateText({
  model: gemini('gemini-2.5-pro'),
  system: 'You are a helpful coding assistant. Always include code examples.',
  prompt: 'How do I read a file in Node.js?',
});

console.log(text);
```

### Conversation History

\*\*AI SDK v5:

```typescript
const result = await generateText({
  model: gemini('gemini-2.5-pro'),
  messages: [
    { role: 'user', content: 'My name is Alice' },
    { role: 'assistant', content: 'Nice to meet you, Alice!' },
    { role: 'user', content: 'What is my name?' },
  ],
});

console.log(result.content[0].text); // Should mention "Alice"
```

**AI SDK v4:**

```typescript
const { text } = await generateText({
  model: gemini('gemini-2.5-pro'),
  messages: [
    { role: 'user', content: 'My name is Alice' },
    { role: 'assistant', content: 'Nice to meet you, Alice!' },
    { role: 'user', content: 'What is my name?' },
  ],
});

console.log(text); // Should mention "Alice"
```

## Supported Models

- **`gemini-3-pro-preview`** - Latest next-generation model with enhanced reasoning capabilities (Preview)
- **`gemini-2.5-pro`** - Previous generation production-ready model (64K output tokens)
- **`gemini-2.5-flash`** - Faster, efficient model (64K output tokens)

**Note**: This provider uses Google Cloud Code endpoints, which may have different model availability and rate limits than the direct Gemini API. The provider defaults to 64K output tokens to take full advantage of Gemini 2.5's capabilities.

## Configuration

### Model Settings

\*\*AI SDK v5:

```typescript
const model = gemini('gemini-2.5-pro', {
  // Standard AI SDK v5 parameters:
  temperature: 0.7,
  maxOutputTokens: 1000,
  topP: 0.95,
});
```

**AI SDK v4:**

```typescript
const model = gemini('gemini-2.5-pro', {
  // Standard AI SDK v4 parameters:
  temperature: 0.7,
  maxTokens: 1000,
  topP: 0.95,
});
```

### Logging Configuration

Control how the provider logs execution information, warnings, and errors. The logger supports multiple log levels and a verbose mode for detailed debugging.

#### Log Levels

The provider supports four log levels:

- **`debug`**: Detailed execution tracing (request/response, token usage, timing)
- **`info`**: General execution flow information (request completion, duration)
- **`warn`**: Warnings about configuration issues or unexpected behavior
- **`error`**: Error messages for failures and exceptions

#### Basic Configuration

```typescript
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';

// Default: logs warnings and errors to console
const gemini = createGeminiProvider({
  authType: 'oauth-personal',
});

// Disable all logging
const model = gemini('gemini-2.5-flash', {
  logger: false,
});

// Custom logger - must implement all four log levels
const customModel = gemini('gemini-2.5-flash', {
  logger: {
    debug: (message) => myLogger.debug('Gemini:', message),
    info: (message) => myLogger.info('Gemini:', message),
    warn: (message) => myLogger.warn('Gemini:', message),
    error: (message) => myLogger.error('Gemini:', message),
  },
});
```

#### Verbose Mode (Debug Logging)

Enable verbose mode to see detailed execution logs, including:

- Request/response tracing
- Message conversion details
- Token usage (input, output, total)
- Request duration and timing
- Finish reasons

**Without verbose mode** (default), only `warn` and `error` messages are logged.
**With verbose mode enabled**, `debug` and `info` messages are also logged.

```typescript
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';
import { generateText } from 'ai';

const gemini = createGeminiProvider({
  authType: 'oauth-personal',
});

// Enable verbose logging for debugging
const model = gemini('gemini-2.5-flash', {
  verbose: true, // Enable debug and info logging
});

const result = await generateText({
  model,
  prompt: 'Hello!',
});
```

#### Custom Logger with Verbose Mode

```typescript
const model = gemini('gemini-2.5-flash', {
  verbose: true,
  logger: {
    debug: (msg) => console.log(`[DEBUG] ${msg}`),
    info: (msg) => console.log(`[INFO] ${msg}`),
    warn: (msg) => console.warn(`[WARN] ${msg}`),
    error: (msg) => console.error(`[ERROR] ${msg}`),
  },
});
```

#### What Gets Logged in Verbose Mode

With `verbose: true`, you'll see detailed execution logs:

```
[DEBUG] Starting doGenerate request with model: gemini-2.5-flash
[DEBUG] Request mode: regular, response format: none
[DEBUG] Converted 2 messages
[DEBUG] Executing generateContent request
[INFO] Request completed - Duration: 1523ms
[DEBUG] Token usage - Input: 245, Output: 128, Total: 373
[DEBUG] Finish reason: stop
```

For streaming requests:

```
[DEBUG] Starting doStream request with model: gemini-2.5-flash
[DEBUG] Stream mode: regular, response format: none
[DEBUG] Converted 2 messages for streaming
[DEBUG] Starting generateContentStream request
[DEBUG] Stream started, processing chunks
[INFO] Stream completed - Duration: 2341ms
[DEBUG] Stream token usage - Input: 512, Output: 256, Total: 768
[DEBUG] Stream finish reason: stop
[DEBUG] Stream finalized, closing stream
```

#### Logger Options

- `undefined` (default): Uses `console.debug`, `console.info`, `console.warn`, and `console.error`
- `false`: Disables all logging
- Custom `Logger` object: Must implement `debug`, `info`, `warn`, and `error` methods

### Provider Options

```typescript
const gemini = createGeminiProvider({
  authType: 'oauth-personal',
  // Uses ~/.gemini/oauth_creds.json by default
});
```

## Key Features

This provider uses Google's Cloud Code endpoints through the Gemini CLI Core library:

- 🔐 Secure OAuth authentication
- ☁️ Access to Google Cloud Code models
- 🚀 Core Vercel AI SDK features
- 📊 Structured output with JSON schemas
- 🔄 Streaming support for real-time responses

## Limitations

- Requires Node.js ≥ 20
- OAuth authentication requires the Gemini CLI to be installed globally
- Rate limits may vary from the direct Gemini API
- Very strict character length constraints in schemas may be challenging for the model
- Image URLs not supported (use base64-encoded images)
- Some AI SDK parameters not supported: `frequencyPenalty`, `presencePenalty`, `seed`
- Only function tools supported (no provider-defined tools)
- **Abort signals have limited support**: While the provider correctly handles abort signals and throws `AbortError`, the underlying `gemini-cli-core` does not support request cancellation. This means aborted requests will continue running in the background until completion, though the provider will throw an `AbortError` as soon as it detects the abort signal

## Contributing

Contributions are welcome! Please see our [Contributing Guide](CONTRIBUTING.md) for details.

## License

MIT - see [LICENSE](LICENSE) for details.


## Links discovered
- [Vercel AI SDK](https://sdk.vercel.ai/docs)
- [@google/gemini-cli-core](https://www.npmjs.com/package/@google/gemini-cli-core)
- [`ai-sdk-v4`](https://github.com/ben-vargas/ai-sdk-provider-gemini-cli/tree/ai-sdk-v4)
- [Google's Terms of Service](https://policies.google.com/terms)
- [CHANGELOG.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//CHANGELOG.md)
- [Examples](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//examples/)
- [API Reference](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//docs/)
- [Authentication Guide](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//docs/gemini-cli-auth-options.md)
- [Migration Guide](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//CHANGELOG.md)
- [examples README](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//examples/README.md)
- [Google AI Studio](https://aistudio.google.com/apikey)
- [Contributing Guide](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//CONTRIBUTING.md)
- [LICENSE](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//LICENSE)
- [<img src="https://img.shields.io/npm/v/ai-sdk-provider-gemini-cli?color=00A79E" alt="npm stable version" />](https://www.npmjs.com/package/ai-sdk-provider-gemini-cli)
- [<img src="https://img.shields.io/npm/unpacked-size/ai-sdk-provider-gemini-cli?color=00A79E" alt="install size" />](https://www.npmjs.com/package/ai-sdk-provider-gemini-cli)
- [<img src="https://img.shields.io/npm/dy/ai-sdk-provider-gemini-cli.svg?color=00A79E" alt="npm downloads" />](https://www.npmjs.com/package/ai-sdk-provider-gemini-cli)
- [<img src="https://img.shields.io/badge/node-%3E%3D20-00A79E" alt="Node.js ≥ 20" />](https://nodejs.org/en/about/releases/)
- [<img src="https://img.shields.io/npm/l/ai-sdk-provider-gemini-cli?color=00A79E" alt="License: MIT" />](https://www.npmjs.com/package/ai-sdk-provider-gemini-cli)
- [<img src="https://awesome.re/mentioned-badge.svg" alt="Mentioned in Awesome Gemini CLI" />](https://github.com/Piebald-AI/awesome-gemini-cli)

--- eslint.config.js ---
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import globals from 'globals';

export default tseslint.config(
  // Base configurations
  eslint.configs.recommended,
  ...tseslint.configs.recommended,
  ...tseslint.configs.recommendedTypeChecked,

  // Global ignores
  {
    ignores: ['dist/**', 'node_modules/**', 'resources/**', '*.config.js', '*.config.ts']
  },

  // TypeScript parser options
  {
    languageOptions: {
      parserOptions: {
        project: './tsconfig.eslint.json',
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },

  // Source files - strict rules
  {
    files: ['src/**/*.ts'],
    rules: {
      '@typescript-eslint/no-explicit-any': 'error',
      '@typescript-eslint/no-floating-promises': 'error',
      '@typescript-eslint/await-thenable': 'error',
      '@typescript-eslint/no-misused-promises': 'error',
      '@typescript-eslint/no-unused-vars': ['error', {
        argsIgnorePattern: '^_',
        varsIgnorePattern: '^_'
      }],
      'no-console': ['warn', { allow: ['warn', 'error'] }],
    },
  },

  // Logger file - allow all console methods for logger implementation
  {
    files: ['src/logger.ts'],
    rules: {
      'no-console': 'off',
    },
  },

  // Test files - relaxed rules
  {
    files: ['**/*.test.ts', '**/*.spec.ts'],
    languageOptions: {
      globals: {
        ...globals.node,
        // Vitest globals
        describe: 'readonly',
        it: 'readonly',
        expect: 'readonly',
        beforeEach: 'readonly',
        afterEach: 'readonly',
        beforeAll: 'readonly',
        afterAll: 'readonly',
        vi: 'readonly',
      },
    },
    rules: {
      '@typescript-eslint/no-explicit-any': 'off',
      '@typescript-eslint/no-unsafe-assignment': 'off',
      '@typescript-eslint/no-unsafe-argument': 'off',
      '@typescript-eslint/no-unsafe-member-access': 'off',
      '@typescript-eslint/no-unsafe-call': 'off',
      '@typescript-eslint/no-unsafe-return': 'off',
      '@typescript-eslint/require-await': 'off',
      '@typescript-eslint/unbound-method': 'off',
      '@typescript-eslint/no-unused-vars': ['error', { 
        argsIgnorePattern: '^_',
        varsIgnorePattern: '^_',
        destructuredArrayIgnorePattern: '^_'
      }],
      'no-console': 'off',
    },
  },

  // Example files - relaxed rules
  {
    files: ['examples/**/*.ts', 'examples/**/*.mjs'],
    languageOptions: {
      globals: {
        ...globals.node,
      },
    },
    rules: {
      'no-console': 'off',
      '@typescript-eslint/no-explicit-any': 'warn',
    },
  },
);

--- tsup.config.ts ---
import { defineConfig } from 'tsup';

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['esm', 'cjs'],
  dts: true,
  sourcemap: true,
  clean: true,
  shims: true,
  external: [
    '@ai-sdk/provider',
    '@ai-sdk/provider-utils',
    '@google/gemini-cli-core',
    'zod'
  ],
  minify: false,
  tsconfig: './tsconfig.build.json',
  outExtension({ format }) {
    return {
      js: format === 'esm' ? '.mjs' : '.js',
    };
  },
});

--- vitest.config.ts ---
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    include: ['src/**/*.{test,spec}.{js,ts,jsx,tsx}'],
    exclude: ['node_modules', 'dist', 'resources'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html'],
      include: ['src/**/*.ts'],
      exclude: [
        'node_modules/**',
        'dist/**',
        'examples/**',
        '**/*.test.ts',
        '**/*.spec.ts',
        'src/__tests__/**',
        '*.config.ts',
        '*.config.js',
      ],
    },
  },
});

--- src/client.ts ---
import { randomUUID } from 'node:crypto';
import type {
  ContentGenerator,
  ContentGeneratorConfig,
} from '@google/gemini-cli-core';
import {
  createContentGenerator,
  createContentGeneratorConfig,
  AuthType,
} from '@google/gemini-cli-core';
import type { GeminiProviderOptions } from './types';

export interface GeminiClient {
  client: ContentGenerator;
  config: ContentGeneratorConfig;
  sessionId: string;
}

/**
 * Initializes the Gemini client with the provided authentication options
 */
export async function initializeGeminiClient(
  options: GeminiProviderOptions,
  modelId: string
): Promise<GeminiClient> {
  // Map our auth types to Gemini CLI Core auth types
  let authType: AuthType | undefined;

  if (options.authType === 'api-key' || options.authType === 'gemini-api-key') {
    authType = AuthType.USE_GEMINI;
  } else if (options.authType === 'vertex-ai') {
    authType = AuthType.USE_VERTEX_AI;
  } else if (
    options.authType === 'oauth' ||
    options.authType === 'oauth-personal'
  ) {
    authType = AuthType.LOGIN_WITH_GOOGLE;
  } else if (options.authType === 'google-auth-library') {
    // Google Auth Library is not directly supported by AuthType enum
    // We'll need to handle this differently or use a default
    authType = AuthType.USE_GEMINI;
  }

  // Generate a stable session ID for this provider instance
  const sessionId = randomUUID();

  // Phase 1: Core config methods with safe defaults
  const baseConfig = {
    // Required methods (currently working)
    getModel: () => modelId,
    getProxy: () =>
      options.proxy ||
      process.env.HTTP_PROXY ||
      process.env.HTTPS_PROXY ||
      undefined,
    getUsageStatisticsEnabled: () => false, // Disable telemetry by default
    getContentGeneratorConfig: () => ({
      authType: authType, // Keep as AuthType | undefined for consistency
      model: modelId,
      apiKey: 'apiKey' in options ? options.apiKey : undefined,
      vertexai: options.authType === 'vertex-ai' ? true : undefined,
      proxy: options.proxy,
    }),

    // Core safety methods - most likely to be called
    getSessionId: () => sessionId,
    getDebugMode: () => false,
    getTelemetryEnabled: () => false,
    getTargetDir: () => process.cwd(),
    getFullContext: () => false,
    getIdeMode: () => false,
    getCoreTools: () => [],
    getExcludeTools: () => [],
    getMaxSessionTurns: () => 100,
    getFileFilteringRespectGitIgnore: () => true,

    // OAuth-specific methods (required for LOGIN_WITH_GOOGLE auth)
    isBrowserLaunchSuppressed: () => false, // Allow browser launch for OAuth flow
  };

  // Phase 2: Proxy wrapper to catch any unknown method calls
  const configMock = new Proxy(baseConfig, {
    get(target, prop) {
      if (prop in target) {
        return target[prop as keyof typeof target];
      }

      // Log unknown method calls (helps identify what else might be needed)
      if (typeof prop === 'string') {
        // Handle different method patterns
        if (
          prop.startsWith('get') ||
          prop.startsWith('is') ||
          prop.startsWith('has')
        ) {
          if (process.env.DEBUG) {
            console.warn(
              `[ai-sdk-provider-gemini-cli] Unknown config method called: ${prop}()`
            );
          }

          // Return safe defaults based on method prefix and naming patterns
          return () => {
            // Boolean methods (is*, has*)
            if (prop.startsWith('is') || prop.startsWith('has')) {
              return false; // Safe default for boolean checks
            }

            // Getter methods (get*)
            if (prop.startsWith('get')) {
              // Return undefined for most unknown methods (safest default)
              if (prop.includes('Enabled') || prop.includes('Mode')) {
                return false; // Booleans default to false
              }
              if (
                prop.includes('Registry') ||
                prop.includes('Client') ||
                prop.includes('Service')
              ) {
                return undefined; // Objects/services default to undefined
              }
              if (prop.includes('Config') || prop.includes('Options')) {
                return {}; // Config objects default to empty
              }
              if (prop.includes('Command') || prop.includes('Path')) {
                return undefined; // Strings default to undefined
              }
              return undefined; // Default fallback
            }

            return undefined; // Fallback for any other pattern
          };
        }
      }

      return undefined;
    },
  });

  // Create the configuration
  const config = await createContentGeneratorConfig(
    configMock as unknown as Parameters<typeof createContentGeneratorConfig>[0],
    authType
  );

  // Apply additional configuration based on auth type
  if (
    (options.authType === 'api-key' || options.authType === 'gemini-api-key') &&
    options.apiKey
  ) {
    config.apiKey = options.apiKey;
  } else if (options.authType === 'vertex-ai' && options.vertexAI) {
    config.vertexai = true;
    // Note: Vertex AI project/location configuration might need to be
    // handled through environment variables or other means
  }

  // Create content generator - pass the configMock as the second parameter and sessionId
  const client = await createContentGenerator(
    config,
    configMock as unknown as Parameters<typeof createContentGenerator>[1],
    sessionId
  );

  return { client, config, sessionId };
}


--- src/error.ts ---
import { APICallError, LoadAPIKeyError } from '@ai-sdk/provider';

/**
 * Custom error metadata for Gemini CLI errors
 */
export interface GeminiCLIErrorMetadata {
  code?: string;
  exitCode?: number;
  stderr?: string;
  promptExcerpt?: string;
}

/**
 * Creates an API call error with Gemini-specific metadata
 */
export function createAPICallError({
  message,
  code,
  exitCode,
  stderr,
  promptExcerpt,
  isRetryable = false,
  statusCode = 500,
}: GeminiCLIErrorMetadata & {
  message: string;
  isRetryable?: boolean;
  statusCode?: number;
}): APICallError {
  return new APICallError({
    url: 'gemini-cli-core://command',
    requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : {},
    statusCode,
    responseHeaders: {},
    message,
    data: {
      code,
      exitCode,
      stderr,
    },
    isRetryable,
  });
}

/**
 * Creates an authentication error
 */
export function createAuthenticationError({
  message,
}: {
  message: string;
}): LoadAPIKeyError {
  return new LoadAPIKeyError({
    message,
  });
}

/**
 * Creates a timeout error
 */
export function createTimeoutError({
  message,
  promptExcerpt,
}: {
  message: string;
  promptExcerpt?: string;
}): APICallError {
  return createAPICallError({
    message,
    code: 'TIMEOUT',
    promptExcerpt,
    isRetryable: true,
    statusCode: 504,
  });
}

/**
 * Checks if an error is an authentication error
 */
export function isAuthenticationError(error: unknown): boolean {
  if (error instanceof LoadAPIKeyError) {
    return true;
  }

  if (error instanceof Error) {
    const message = error.message.toLowerCase();
    return (
      message.includes('unauthorized') ||
      message.includes('authentication') ||
      message.includes('api key') ||
      message.includes('credentials')
    );
  }

  return false;
}

/**
 * Checks if an error is a timeout error
 */
export function isTimeoutError(error: unknown): boolean {
  if (error instanceof APICallError) {
    return (
      error.statusCode === 504 ||
      (error.data as GeminiCLIErrorMetadata)?.code === 'TIMEOUT'
    );
  }

  if (error instanceof Error) {
    const message = error.message.toLowerCase();
    return message.includes('timeout') || message.includes('timed out');
  }

  return false;
}

/**
 * Gets error metadata from an error
 */
export function getErrorMetadata(
  error: unknown
): GeminiCLIErrorMetadata | undefined {
  if (error instanceof APICallError) {
    return error.data as GeminiCLIErrorMetadata;
  }

  return undefined;
}

/**
 * Maps Gemini errors to Vercel AI SDK errors (v5 pattern)
 */
export function mapGeminiError(error: unknown): APICallError | LoadAPIKeyError {
  if (error instanceof Error) {
    // Don't wrap abort errors - they should pass through unchanged
    if (error.name === 'AbortError') {
      throw error;
    }

    const message = error.message.toLowerCase();

    // Check for authentication errors
    if (isAuthenticationError(error)) {
      return createAuthenticationError({
        message: error.message,
      });
    }

    // Check for rate limit errors
    if (message.includes('rate limit') || message.includes('quota')) {
      return createAPICallError({
        message: error.message,
        code: 'RATE_LIMIT',
        isRetryable: true,
        statusCode: 429,
      });
    }

    // Check for timeout errors
    if (isTimeoutError(error)) {
      return createTimeoutError({
        message: error.message,
      });
    }

    // Check for model not found (check this before general invalid errors)
    if (
      message.includes('not found') ||
      message.includes('no such model') ||
      (message.includes('model') &&
        (message.includes('invalid') || message.includes('not found')))
    ) {
      return createAPICallError({
        message: error.message,
        code: 'MODEL_NOT_FOUND',
        isRetryable: false,
        statusCode: 404,
      });
    }

    // Check for invalid request errors
    if (message.includes('invalid') || message.includes('bad request')) {
      return createAPICallError({
        message: error.message,
        code: 'INVALID_REQUEST',
        isRetryable: false,
        statusCode: 400,
      });
    }

    // Default to internal server error
    return createAPICallError({
      message: error.message,
      code: 'INTERNAL_ERROR',
      isRetryable: true,
      statusCode: 500,
    });
  }

  // Unknown error type
  return createAPICallError({
    message: 'An unknown error occurred',
    code: 'UNKNOWN_ERROR',
    isRetryable: true,
    statusCode: 500,
  });
}


--- src/gemini-language-model.ts ---
import { randomUUID } from 'node:crypto';
import type {
  LanguageModelV2,
  LanguageModelV2CallOptions,
  LanguageModelV2CallWarning,
  LanguageModelV2FinishReason,
  LanguageModelV2FunctionTool,
  LanguageModelV2StreamPart,
  LanguageModelV2Content,
  LanguageModelV2Usage,
} from '@ai-sdk/provider';
import type {
  ContentGenerator,
  ContentGeneratorConfig,
} from '@google/gemini-cli-core';
import type {
  GenerateContentParameters,
  GenerateContentConfig,
} from '@google/genai';
import { initializeGeminiClient } from './client';
import { mapPromptToGeminiFormat } from './message-mapper';
import { mapGeminiToolConfig, mapToolsToGeminiFormat } from './tool-mapper';
import { mapGeminiError } from './error';
import type { GeminiProviderOptions, Logger } from './types';
import { getLogger, createVerboseLogger } from './logger';

export interface GeminiLanguageModelOptions {
  modelId: string;
  providerOptions: GeminiProviderOptions;
  settings?: Record<string, unknown> & {
    logger?: Logger | false;
    verbose?: boolean;
  };
}

/**
 * Map Gemini finish reasons to Vercel AI SDK finish reasons
 */
function mapGeminiFinishReason(
  geminiReason?: string
): LanguageModelV2FinishReason {
  switch (geminiReason) {
    case 'STOP':
      return 'stop';
    case 'MAX_TOKENS':
      return 'length';
    case 'SAFETY':
    case 'RECITATION':
      return 'content-filter';
    case 'OTHER':
      return 'other';
    default:
      return 'unknown';
  }
}

/**
 * Prepare generation config with proper handling for JSON mode.
 *
 * When JSON response format is requested WITHOUT a schema, we downgrade to
 * text/plain and emit a warning. This aligns with Claude-code provider behavior
 * and prevents raw fenced JSON from leaking to clients.
 *
 * When a schema IS provided, we use native responseJsonSchema for structured output.
 */
function prepareGenerationConfig(
  options: LanguageModelV2CallOptions,
  settings?: Record<string, unknown>
): {
  generationConfig: GenerateContentConfig;
  warnings: LanguageModelV2CallWarning[];
} {
  const warnings: LanguageModelV2CallWarning[] = [];

  // Extract schema if JSON mode with schema is requested
  const responseFormat = options.responseFormat;
  const isJsonMode = responseFormat?.type === 'json';
  const schema = isJsonMode ? responseFormat.schema : undefined;
  const hasSchema = isJsonMode && schema !== undefined;

  // JSON without schema: downgrade to text/plain with warning
  if (isJsonMode && !hasSchema) {
    warnings.push({
      type: 'unsupported-setting',
      setting: 'responseFormat',
      details:
        'JSON response format without a schema is not supported. Treating as plain text. Provide a schema for structured output.',
    });
  }

  const generationConfig: GenerateContentConfig = {
    temperature:
      options.temperature ?? (settings?.temperature as number | undefined),
    topP: options.topP ?? (settings?.topP as number | undefined),
    topK: options.topK ?? (settings?.topK as number | undefined),
    maxOutputTokens:
      options.maxOutputTokens ??
      (settings?.maxOutputTokens as number | undefined),
    stopSequences: options.stopSequences,
    // Only use application/json when we have a schema to enforce it
    responseMimeType: hasSchema ? 'application/json' : 'text/plain',
    // Pass schema directly to Gemini API for native structured output
    responseJsonSchema: hasSchema ? schema : undefined,
    toolConfig: mapGeminiToolConfig(options),
  };

  return { generationConfig, warnings };
}

export class GeminiLanguageModel implements LanguageModelV2 {
  readonly specificationVersion = 'v2' as const;
  readonly provider = 'gemini-cli-core';
  readonly defaultObjectGenerationMode = 'json' as const;
  readonly supportsImageUrls = false; // CLI Core uses base64 data, not URLs
  readonly supportedUrls = {}; // No native URL support
  readonly supportsStructuredOutputs = true; // Native Gemini responseJsonSchema support

  private contentGenerator?: ContentGenerator;
  private config?: ContentGeneratorConfig;
  private initPromise?: Promise<void>;

  readonly modelId: string;
  readonly settings?: Record<string, unknown>;
  private providerOptions: GeminiProviderOptions;
  private logger: Logger;

  constructor(options: GeminiLanguageModelOptions) {
    this.modelId = options.modelId;
    this.providerOptions = options.providerOptions;
    this.settings = options.settings;

    // Create logger that respects verbose setting
    const baseLogger = getLogger(options.settings?.logger);
    this.logger = createVerboseLogger(
      baseLogger,
      options.settings?.verbose ?? false
    );
  }

  private async ensureInitialized(): Promise<{
    contentGenerator: ContentGenerator;
    config: ContentGeneratorConfig;
  }> {
    if (this.contentGenerator && this.config) {
      return { contentGenerator: this.contentGenerator, config: this.config };
    }

    if (!this.initPromise) {
      this.initPromise = this.initialize();
    }

    await this.initPromise;
    return { contentGenerator: this.contentGenerator!, config: this.config! };
  }

  private async initialize(): Promise<void> {
    try {
      const { client, config } = await initializeGeminiClient(
        this.providerOptions,
        this.modelId
      );
      this.contentGenerator = client;
      this.config = config;
    } catch (error) {
      throw new Error(`Failed to initialize Gemini model: ${String(error)}`);
    }
  }

  /**
   * Non-streaming generation method
   */
  async doGenerate(options: LanguageModelV2CallOptions): Promise<{
    content: LanguageModelV2Content[];
    finishReason: LanguageModelV2FinishReason;
    usage: LanguageModelV2Usage;
    rawCall: {
      rawPrompt: unknown;
      rawSettings: Record<string, unknown>;
    };
    rawResponse?: {
      body?: unknown;
    };
    response?: {
      id?: string;
      timestamp?: Date;
      modelId?: string;
    };
    warnings: LanguageModelV2CallWarning[];
  }> {
    this.logger.debug(
      `[gemini-cli] Starting doGenerate request with model: ${this.modelId}`
    );

    try {
      const { contentGenerator } = await this.ensureInitialized();

      // Map the prompt to Gemini format
      const { contents, systemInstruction } = mapPromptToGeminiFormat(options);

      this.logger.debug(
        `[gemini-cli] Request mode: ${options.responseFormat?.type === 'json' ? 'object-json' : 'regular'}, response format: ${options.responseFormat?.type ?? 'none'}`
      );

      this.logger.debug(
        `[gemini-cli] Converted ${options.prompt.length} messages`
      );

      // Prepare generation config with proper JSON mode handling
      // (downgrades to text/plain with warning if JSON requested without schema)
      const { generationConfig, warnings } = prepareGenerationConfig(
        options,
        this.settings
      );

      // Map tools if provided in regular mode
      let tools;
      if (options.tools) {
        // Filter to only function tools (not provider-defined tools)
        const functionTools = options.tools.filter(
          (tool): tool is LanguageModelV2FunctionTool =>
            tool.type === 'function'
        );
        if (functionTools.length > 0) {
          tools = mapToolsToGeminiFormat(functionTools);
        }
      }

      // Create the request parameters
      const request: GenerateContentParameters = {
        model: this.modelId,
        contents,
        config: {
          ...generationConfig,
          systemInstruction: systemInstruction,
          tools: tools,
        },
      };

      // Set up abort handling
      let abortListener: (() => void) | undefined;
      if (options.abortSignal) {
        // Check if already aborted
        if (options.abortSignal.aborted) {
          const abortError = new Error('Request aborted');
          abortError.name = 'AbortError';
          throw abortError;
        }

        // Set up listener for abort signal
        // LIMITATION: The gemini-cli-core library doesn't expose request cancellation
        // We can only check abort status before/after the request, not cancel in-flight
        abortListener = () => {
          // Track abort state - actual cancellation happens via status checks
        };
        options.abortSignal.addEventListener('abort', abortListener, {
          once: true,
        });
      }

      // Generate content (new signature requires userPromptId)
      let response;
      const startTime = Date.now();
      try {
        this.logger.debug('[gemini-cli] Executing generateContent request');

        response = await contentGenerator.generateContent(
          request,
          randomUUID()
        );

        const duration = Date.now() - startTime;
        this.logger.info(
          `[gemini-cli] Request completed - Duration: ${duration}ms`
        );

        // Check if aborted during generation
        if (options.abortSignal?.aborted) {
          const abortError = new Error('Request aborted');
          abortError.name = 'AbortError';
          throw abortError;
        }
      } finally {
        // Clean up abort listener
        if (options.abortSignal && abortListener) {
          options.abortSignal.removeEventListener('abort', abortListener);
        }
      }

      // Extract the result
      const candidate = response.candidates?.[0];
      const responseContent = candidate?.content;

      // Build content array for v2 format
      const content: LanguageModelV2Content[] = [];

      if (responseContent?.parts) {
        for (const part of responseContent.parts) {
          if (part.text) {
            // With native responseJsonSchema, the output is already clean JSON
            content.push({
              type: 'text',
              text: part.text,
            });
          } else if (part.functionCall) {
            content.push({
              type: 'tool-call',
              toolCallId: randomUUID(),
              toolName: part.functionCall.name || '',
              input: JSON.stringify(part.functionCall.args || {}),
            } as LanguageModelV2Content);
          }
        }
      }

      // Calculate token usage
      const inputTokens = response.usageMetadata?.promptTokenCount || 0;
      const outputTokens = response.usageMetadata?.candidatesTokenCount || 0;
      const totalTokens = inputTokens + outputTokens;

      const usage: LanguageModelV2Usage = {
        inputTokens,
        outputTokens,
        totalTokens,
      };

      this.logger.debug(
        `[gemini-cli] Token usage - Input: ${inputTokens}, Output: ${outputTokens}, Total: ${totalTokens}`
      );

      const finishReason = mapGeminiFinishReason(candidate?.finishReason);
      this.logger.debug(`[gemini-cli] Finish reason: ${finishReason}`);

      return {
        content,
        finishReason,
        usage,
        rawCall: {
          rawPrompt: { contents, systemInstruction, generationConfig, tools },
          rawSettings: generationConfig as Record<string, unknown>,
        },
        rawResponse: {
          body: response,
        },
        response: {
          id: randomUUID(),
          timestamp: new Date(),
          modelId: this.modelId,
        },
        warnings,
      };
    } catch (error) {
      this.logger.debug(
        `[gemini-cli] Error during doGenerate: ${error instanceof Error ? error.message : String(error)}`
      );
      throw mapGeminiError(error);
    }
  }

  /**
   * Streaming generation method
   */
  async doStream(options: LanguageModelV2CallOptions): Promise<{
    stream: ReadableStream<LanguageModelV2StreamPart>;
    rawCall: {
      rawPrompt: unknown;
      rawSettings: Record<string, unknown>;
    };
  }> {
    this.logger.debug(
      `[gemini-cli] Starting doStream request with model: ${this.modelId}`
    );

    try {
      const { contentGenerator } = await this.ensureInitialized();

      // Map the prompt to Gemini format
      const { contents, systemInstruction } = mapPromptToGeminiFormat(options);

      this.logger.debug(
        `[gemini-cli] Stream mode: ${options.responseFormat?.type === 'json' ? 'object-json' : 'regular'}, response format: ${options.responseFormat?.type ?? 'none'}`
      );

      this.logger.debug(
        `[gemini-cli] Converted ${options.prompt.length} messages for streaming`
      );

      // Prepare generation config with proper JSON mode handling
      // (downgrades to text/plain with warning if JSON requested without schema)
      const { generationConfig, warnings } = prepareGenerationConfig(
        options,
        this.settings
      );

      // Map tools if provided in regular mode
      let tools;
      if (options.tools) {
        // Filter to only function tools (not provider-defined tools)
        const functionTools = options.tools.filter(
          (tool): tool is LanguageModelV2FunctionTool =>
            tool.type === 'function'
        );
        if (functionTools.length > 0) {
          tools = mapToolsToGeminiFormat(functionTools);
        }
      }

      // Create the request parameters
      const request: GenerateContentParameters = {
        model: this.modelId,
        contents,
        config: {
          ...generationConfig,
          systemInstruction: systemInstruction,
          tools: tools,
        },
      };

      // Set up abort handling
      let abortListener: (() => void) | undefined;
      if (options.abortSignal) {
        // Check if already aborted
        if (options.abortSignal.aborted) {
          const abortError = new Error('Request aborted');
          abortError.name = 'AbortError';
          throw abortError;
        }

        // Set up listener for abort signal
        // LIMITATION: The gemini-cli-core library doesn't expose stream cancellation
        // We can only check abort status during iteration, not cancel the underlying stream
        abortListener = () => {
          // Track abort state - actual cancellation happens via status checks
        };
        options.abortSignal.addEventListener('abort', abortListener, {
          once: true,
        });
      }

      // Create streaming response (new signature requires userPromptId)
      let streamResponse;
      try {
        this.logger.debug(
          '[gemini-cli] Starting generateContentStream request'
        );

        streamResponse = await contentGenerator.generateContentStream(
          request,
          randomUUID()
        );

        // Check if aborted during stream creation
        if (options.abortSignal?.aborted) {
          const abortError = new Error('Request aborted');
          abortError.name = 'AbortError';
          throw abortError;
        }
      } catch (error) {
        // Clean up abort listener on error
        if (options.abortSignal && abortListener) {
          options.abortSignal.removeEventListener('abort', abortListener);
        }
        throw error;
      }

      // Capture modelId, logger, and warnings for use in stream
      const modelId = this.modelId;
      const logger = this.logger;
      const streamWarnings = warnings;

      // Transform the stream to AI SDK v5 format
      const stream = new ReadableStream<LanguageModelV2StreamPart>({
        async start(controller) {
          try {
            // Check for abort signal in stream
            if (options.abortSignal?.aborted) {
              const abortError = new Error('Request aborted');
              abortError.name = 'AbortError';
              controller.error(abortError);
              return;
            }
            let totalInputTokens = 0;
            let totalOutputTokens = 0;

            // Emit stream-start event with any warnings
            controller.enqueue({
              type: 'stream-start',
              warnings: streamWarnings,
            });

            const streamStartTime = Date.now();
            logger.debug('[gemini-cli] Stream started, processing chunks');

            for await (const chunk of streamResponse) {
              // Check if aborted during streaming
              if (options.abortSignal?.aborted) {
                const abortError = new Error('Request aborted');
                abortError.name = 'AbortError';
                controller.error(abortError);
                break;
              }

              const candidate = chunk.candidates?.[0];
              const content = candidate?.content;

              // Update token counts if available
              if (chunk.usageMetadata) {
                totalInputTokens = chunk.usageMetadata.promptTokenCount || 0;
                totalOutputTokens =
                  chunk.usageMetadata.candidatesTokenCount || 0;
              }

              if (content?.parts) {
                for (const part of content.parts) {
                  if (part.text) {
                    // With native responseJsonSchema, stream text directly
                    // (output is already clean JSON when schema is provided)
                    controller.enqueue({
                      type: 'text-delta',
                      id: randomUUID(),
                      delta: part.text,
                    });
                  } else if (part.functionCall) {
                    // Emit tool call as a single event
                    controller.enqueue({
                      type: 'tool-call',
                      toolCallId: randomUUID(),
                      toolName: part.functionCall.name || '',
                      input: JSON.stringify(part.functionCall.args || {}),
                    });
                  }
                }
              }

              if (candidate?.finishReason) {
                const duration = Date.now() - streamStartTime;
                logger.info(
                  `[gemini-cli] Stream completed - Duration: ${duration}ms`
                );

                logger.debug(
                  `[gemini-cli] Stream token usage - Input: ${totalInputTokens}, Output: ${totalOutputTokens}, Total: ${totalInputTokens + totalOutputTokens}`
                );

                const finishReason = mapGeminiFinishReason(
                  candidate.finishReason
                );
                logger.debug(
                  `[gemini-cli] Stream finish reason: ${finishReason}`
                );

                // Emit response metadata
                controller.enqueue({
                  type: 'response-metadata',
                  id: randomUUID(),
                  timestamp: new Date(),
                  modelId: modelId,
                });

                // Emit finish event
                controller.enqueue({
                  type: 'finish',
                  finishReason,
                  usage: {
                    inputTokens: totalInputTokens,
                    outputTokens: totalOutputTokens,
                    totalTokens: totalInputTokens + totalOutputTokens,
                  },
                });
              }
            }

            logger.debug('[gemini-cli] Stream finalized, closing stream');
            controller.close();
          } catch (error) {
            logger.debug(
              `[gemini-cli] Error during doStream: ${error instanceof Error ? error.message : String(error)}`
            );
            controller.error(mapGeminiError(error));
          } finally {
            // Clean up abort listener
            if (options.abortSignal && abortListener) {
              options.abortSignal.removeEventListener('abort', abortListener);
            }
          }
        },
        cancel: () => {
          // Clean up abort listener on cancel
          if (options.abortSignal && abortListener) {
            options.abortSignal.removeEventListener('abort', abortListener);
          }
        },
      });

      return {
        stream,
        rawCall: {
          rawPrompt: { contents, systemInstruction, generationConfig, tools },
          rawSettings: generationConfig as Record<string, unknown>,
        },
      };
    } catch (error) {
      this.logger.debug(
        `[gemini-cli] Error creating stream: ${error instanceof Error ? error.message : String(error)}`
      );
      throw mapGeminiError(error);
    }
  }
}


--- src/gemini-provider.ts ---
import type {
  ProviderV2,
  LanguageModelV2,
  EmbeddingModelV2,
  ImageModelV2,
} from '@ai-sdk/provider';
import { NoSuchModelError } from '@ai-sdk/provider';
import { GeminiLanguageModel } from './gemini-language-model';
import type { GeminiProviderOptions } from './types';
import { validateAuthOptions } from './validation';

export interface GeminiProvider extends ProviderV2 {
  (modelId: string, settings?: Record<string, unknown>): LanguageModelV2;
  languageModel(
    modelId: string,
    settings?: Record<string, unknown>
  ): LanguageModelV2;
  chat(modelId: string, settings?: Record<string, unknown>): LanguageModelV2;
  textEmbeddingModel(modelId: string): EmbeddingModelV2<string>;
  imageModel(modelId: string): ImageModelV2;
}

/**
 * Creates a new Gemini provider instance.
 *
 * @param options - Configuration options for the provider
 * @returns A configured provider function
 * @throws Error if authentication options are invalid
 *
 * @example
 * ```typescript
 * // Using API key authentication
 * const gemini = createGeminiProvider({
 *   authType: 'gemini-api-key',
 *   apiKey: process.env.GEMINI_API_KEY
 * });
 *
 * // Use with Vercel AI SDK
 * const model = gemini('gemini-1.5-flash');
 * const result = await generateText({
 *   model,
 *   prompt: 'Hello, world!'
 * });
 * ```
 */
export function createGeminiProvider(
  options: GeminiProviderOptions = {}
): GeminiProvider {
  // Validate authentication options
  const validatedOptions = validateAuthOptions(options);

  // Create the language model factory function
  const createLanguageModel = (
    modelId: string,
    settings?: Record<string, unknown>
  ) => {
    return new GeminiLanguageModel({
      modelId,
      providerOptions: validatedOptions,
      settings: {
        maxOutputTokens: 65536, // 64K output tokens for Gemini 2.5 models
        ...settings,
      },
    });
  };

  // Create the provider function
  const provider = Object.assign(
    function (modelId: string, settings?: Record<string, unknown>) {
      if (new.target) {
        throw new Error(
          'The provider function cannot be called with the new keyword.'
        );
      }

      return createLanguageModel(modelId, settings);
    },
    {
      languageModel: createLanguageModel,
      chat: createLanguageModel,
      textEmbeddingModel: (modelId: string): never => {
        throw new NoSuchModelError({
          modelId,
          modelType: 'textEmbeddingModel',
          message: `Gemini provider does not support text embedding models.`,
        });
      },
      imageModel: (modelId: string): never => {
        throw new NoSuchModelError({
          modelId,
          modelType: 'imageModel',
          message: `Gemini provider does not support image models.`,
        });
      },
    }
  ) as GeminiProvider;

  return provider;
}


--- src/index.ts ---
// Main exports
export { createGeminiProvider } from './gemini-provider';

// Type exports
export type { GeminiProvider } from './gemini-provider';
export type { GeminiProviderOptions, Logger } from './types';

// Legacy compatibility exports (for backward compatibility)
export { createGeminiProvider as createGeminiCliCoreProvider } from './gemini-provider';
export type { GeminiProvider as GeminiCliCoreProvider } from './gemini-provider';
export type { GeminiProviderOptions as GeminiCliCoreProviderOptions } from './types';

// Re-export types from AI SDK for convenience
export type {
  LanguageModelV2,
  LanguageModelV2FunctionTool,
  LanguageModelV2ToolCall,
  LanguageModelV2FinishReason,
  LanguageModelV2CallOptions,
  LanguageModelV2CallWarning,
  LanguageModelV2StreamPart,
  LanguageModelV2Content,
  LanguageModelV2Usage,
  ProviderV2,
} from '@ai-sdk/provider';


--- src/logger.ts ---
import type { Logger } from './types.js';

/**
 * Default logger that uses console with level tags.
 */
const defaultLogger: Logger = {
  debug: (message: string) => console.debug(`[DEBUG] ${message}`),
  info: (message: string) => console.info(`[INFO] ${message}`),
  warn: (message: string) => console.warn(`[WARN] ${message}`),
  error: (message: string) => console.error(`[ERROR] ${message}`),
};

/**
 * No-op logger that discards all messages.
 */
const noopLogger: Logger = {
  debug: () => {},
  info: () => {},
  warn: () => {},
  error: () => {},
};

/**
 * Gets the appropriate logger instance based on the provided option.
 *
 * @param logger - Logger configuration: undefined (default console), false (no logging), or custom Logger
 * @returns Logger instance to use for all logging operations
 */
export function getLogger(logger: Logger | false | undefined): Logger {
  if (logger === false) {
    return noopLogger;
  }

  if (logger === undefined) {
    return defaultLogger;
  }

  return logger;
}

/**
 * Creates a verbose-aware logger that only logs debug/info when verbose is enabled.
 * Warn and error are always logged regardless of verbose setting.
 *
 * When verbose is false (default), debug and info calls are suppressed.
 * When verbose is true, all log levels are passed through to the underlying logger.
 *
 * @param logger - The underlying logger to wrap
 * @param verbose - Whether to enable verbose (debug/info) logging. Defaults to false.
 * @returns A logger that filters debug/info based on verbose mode
 *
 * @example
 * ```typescript
 * const baseLogger = getLogger(undefined); // console logger
 * const verboseLogger = createVerboseLogger(baseLogger, true);
 *
 * verboseLogger.debug('This will be logged');
 * verboseLogger.info('This will be logged');
 * verboseLogger.warn('Always logged');
 * verboseLogger.error('Always logged');
 * ```
 */
export function createVerboseLogger(
  logger: Logger,
  verbose: boolean = false
): Logger {
  if (verbose) {
    // When verbose is enabled, pass through all log levels
    return logger;
  }

  // When verbose is disabled, suppress debug and info, but keep warn and error
  return {
    debug: () => {}, // Suppressed in non-verbose mode
    info: () => {}, // Suppressed in non-verbose mode
    warn: logger.warn.bind(logger),
    error: logger.error.bind(logger),
  };
}


--- src/message-mapper.ts ---
import type {
  LanguageModelV2CallOptions,
  LanguageModelV2FilePart,
  LanguageModelV2Message,
} from '@ai-sdk/provider';
import type { Content, Part } from '@google/genai';

export interface GeminiPromptResult {
  contents: Content[];
  systemInstruction?: Content;
}

/**
 * Maps Vercel AI SDK messages to Gemini format
 *
 * Note: Schema is now passed directly via responseJsonSchema in the generation config,
 * so we no longer inject schema instructions into the prompt.
 */
export function mapPromptToGeminiFormat(
  options: LanguageModelV2CallOptions
): GeminiPromptResult {
  const messages = options.prompt;
  const contents: Content[] = [];
  let systemInstruction: Content | undefined;

  for (const message of messages) {
    switch (message.role) {
      case 'system':
        // Gemini uses a separate systemInstruction field
        systemInstruction = {
          role: 'user',
          parts: [{ text: message.content }],
        };
        break;

      case 'user':
        contents.push(mapUserMessage(message));
        break;

      case 'assistant':
        contents.push(mapAssistantMessage(message));
        break;

      case 'tool': {
        // Tool results in v5 are part of tool messages
        const parts: Part[] = [];
        for (const part of message.content) {
          if (part.type === 'tool-result') {
            parts.push({
              functionResponse: {
                name: part.toolName,
                response: (typeof part.output === 'string'
                  ? { result: part.output }
                  : part.output) as Record<string, unknown>,
              },
            });
          }
        }
        contents.push({
          role: 'user',
          parts,
        });
        break;
      }
    }
  }

  return { contents, systemInstruction };
}

/**
 * Maps a user message to Gemini format
 */
function mapUserMessage(
  message: LanguageModelV2Message & { role: 'user' }
): Content {
  const parts: Part[] = [];

  for (const part of message.content) {
    switch (part.type) {
      case 'text':
        parts.push({ text: part.text });
        break;

      case 'file': {
        // Handle file parts (images, etc.)
        const mediaType = part.mediaType || 'application/octet-stream';
        if (mediaType.startsWith('image/')) {
          parts.push(mapImagePart(part));
        } else {
          throw new Error(`Unsupported file type: ${mediaType}`);
        }
        break;
      }
    }
  }

  return { role: 'user', parts };
}

/**
 * Maps an assistant message to Gemini format
 */
function mapAssistantMessage(
  message: LanguageModelV2Message & { role: 'assistant' }
): Content {
  const parts: Part[] = [];

  for (const part of message.content) {
    switch (part.type) {
      case 'text':
        parts.push({ text: part.text });
        break;

      case 'tool-call':
        // In v5, tool calls have input as an object already
        parts.push({
          functionCall: {
            name: part.toolName,
            args: (part.input || {}) as Record<string, unknown>,
          },
        });
        break;
    }
  }

  return { role: 'model', parts };
}

/**
 * Maps an image part to Gemini format
 */
function mapImagePart(part: LanguageModelV2FilePart): Part {
  if (part.data instanceof URL) {
    throw new Error(
      'URL images are not supported by Gemini CLI Core. Please provide base64-encoded image data.'
    );
  }

  // Extract mime type and base64 data
  const mimeType = part.mediaType || 'image/jpeg';
  let base64Data: string;

  if (typeof part.data === 'string') {
    // Already base64 encoded
    base64Data = part.data;
  } else if (part.data instanceof Uint8Array) {
    // Convert Uint8Array to base64
    base64Data = Buffer.from(part.data).toString('base64');
  } else {
    throw new Error('Unsupported image format');
  }

  return {
    inlineData: {
      mimeType,
      data: base64Data,
    },
  };
}


--- src/tool-mapper.ts ---
import type {
  LanguageModelV2CallOptions,
  LanguageModelV2FunctionTool,
  LanguageModelV2ToolChoice,
} from '@ai-sdk/provider';
import {
  Tool,
  FunctionDeclaration,
  Schema,
  ToolConfig,
  FunctionCallingConfigMode,
} from '@google/genai';
import { z } from 'zod';

// Type for JSON Schema objects with common properties
interface JsonSchemaObject {
  $schema?: string;
  $ref?: string;
  $defs?: unknown;
  definitions?: unknown;
  properties?: Record<string, unknown>;
  items?: unknown;
  additionalProperties?: unknown;
  allOf?: unknown[];
  anyOf?: unknown[];
  oneOf?: unknown[];
  [key: string]: unknown;
}

/**
 * Maps Vercel AI SDK tools to Gemini format
 */
export function mapToolsToGeminiFormat(
  tools: LanguageModelV2FunctionTool[]
): Tool[] {
  const functionDeclarations: FunctionDeclaration[] = [];

  for (const tool of tools) {
    functionDeclarations.push({
      name: tool.name,
      description: tool.description,
      parameters: convertToolParameters(tool.inputSchema),
    });
  }

  return [{ functionDeclarations }];
}

/**
 * Attempts to convert a Zod schema to JSON Schema using available methods
 */
function convertZodToJsonSchema(zodSchema: z.ZodSchema): unknown {
  // Try Zod v4's native toJSONSchema function first (if available)
  const zodWithToJSONSchema = z as unknown as {
    toJSONSchema?: (schema: z.ZodSchema) => unknown;
  };

  if (
    zodWithToJSONSchema.toJSONSchema &&
    typeof zodWithToJSONSchema.toJSONSchema === 'function'
  ) {
    try {
      // Zod v4 uses z.toJSONSchema(schema) as a standalone function
      return zodWithToJSONSchema.toJSONSchema(zodSchema);
    } catch {
      // Method exists but failed, try fallback
    }
  }

  // Try zod-to-json-schema for Zod v3 compatibility
  try {
    // Lazy load zod-to-json-schema to avoid import errors with Zod v4
    // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
    const zodToJsonSchemaModule = require('zod-to-json-schema');
    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
    return zodToJsonSchemaModule.zodToJsonSchema(zodSchema);
  } catch {
    // zod-to-json-schema not available or not compatible
  }

  // No conversion method available
  console.warn(
    'Unable to convert Zod schema to JSON Schema. ' +
      'For Zod v3, install zod-to-json-schema. ' +
      'For Zod v4, use z.toJSONSchema() function.'
  );

  // Return a basic object schema as fallback
  return { type: 'object' };
}

/**
 * Converts tool parameters from Zod schema or JSON schema to Gemini format
 */
function convertToolParameters(parameters: unknown): Schema {
  // If it's already a plain object (JSON schema), clean it
  if (isJsonSchema(parameters)) {
    return cleanJsonSchema(parameters as JsonSchemaObject) as Schema;
  }

  // If it's a Zod schema, convert to JSON schema first
  if (isZodSchema(parameters)) {
    const jsonSchema = convertZodToJsonSchema(parameters as z.ZodSchema);
    return cleanJsonSchema(jsonSchema as JsonSchemaObject) as Schema;
  }

  // Return a basic schema if we can't identify the format
  return parameters as Schema;
}

/**
 * Checks if an object is a JSON schema
 */
function isJsonSchema(obj: unknown): boolean {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    ('type' in obj || 'properties' in obj || '$schema' in obj)
  );
}

/**
 * Checks if an object is a Zod schema
 */
function isZodSchema(obj: unknown): obj is z.ZodTypeAny {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    '_def' in obj &&
    typeof (obj as z.ZodTypeAny)._def === 'object'
  );
}

/**
 * Cleans JSON schema for Gemini compatibility
 * Removes $schema and other metadata that Gemini doesn't support
 */
function cleanJsonSchema(schema: JsonSchemaObject): JsonSchemaObject {
  if (typeof schema !== 'object' || schema === null) {
    return schema;
  }

  const cleaned = { ...schema };

  // Remove $schema property
  delete cleaned.$schema;
  delete cleaned.$ref;
  delete cleaned.$defs;
  delete cleaned.definitions;

  // Recursively clean nested schemas
  if (cleaned.properties && typeof cleaned.properties === 'object') {
    const cleanedProps: Record<string, unknown> = {};
    for (const [key, value] of Object.entries(cleaned.properties)) {
      cleanedProps[key] = cleanJsonSchema(value as JsonSchemaObject);
    }
    cleaned.properties = cleanedProps;
  }

  if (cleaned.items) {
    cleaned.items = cleanJsonSchema(cleaned.items as JsonSchemaObject);
  }

  if (
    cleaned.additionalProperties &&
    typeof cleaned.additionalProperties === 'object'
  ) {
    cleaned.additionalProperties = cleanJsonSchema(
      cleaned.additionalProperties as JsonSchemaObject
    );
  }

  // Clean arrays
  for (const key of ['allOf', 'anyOf', 'oneOf'] as const) {
    const arrayProp = cleaned[key];
    if (Array.isArray(arrayProp)) {
      cleaned[key] = arrayProp.map((item) =>
        cleanJsonSchema(item as JsonSchemaObject)
      );
    }
  }

  return cleaned;
}

/**
 * Maps Vercel AI SDK tool config options to Gemini format
 */
export function mapGeminiToolConfig(
  options: LanguageModelV2CallOptions
): ToolConfig | undefined {
  if (options.toolChoice) {
    // Restrict allowed function names when a specific tool is forced.
    // Gemini expects that when forcing a tool call, the function name is
    // provided via `allowedFunctionNames` while `mode` is set to ANY.
    const allowedFunctionNames =
      options.toolChoice.type === 'tool'
        ? [options.toolChoice.toolName]
        : undefined;

    return {
      functionCallingConfig: {
        allowedFunctionNames,
        mode: mapToolChoiceToGeminiFormat(options.toolChoice),
      },
    };
  }
  return undefined;
}

function mapToolChoiceToGeminiFormat(
  toolChoice: LanguageModelV2ToolChoice
): FunctionCallingConfigMode {
  switch (toolChoice.type) {
    case 'auto':
      return FunctionCallingConfigMode.AUTO;
    case 'none':
      return FunctionCallingConfigMode.NONE;
    case 'required':
    case 'tool':
      return FunctionCallingConfigMode.ANY;
    default:
      // this should never happen if types are correct
      return FunctionCallingConfigMode.MODE_UNSPECIFIED;
  }
}


--- src/types.ts ---
import type { GoogleAuth } from 'google-auth-library';

/**
 * Base options available for all authentication types
 */
export interface BaseProviderOptions {
  /**
   * HTTP proxy URL to use for requests
   * Can also be set via HTTP_PROXY or HTTPS_PROXY environment variables
   */
  proxy?: string;
}

/**
 * Provider options for configuring Gemini authentication and behavior
 */
export type GeminiProviderOptions =
  | (GeminiApiKeyAuth & BaseProviderOptions)
  | (VertexAIAuth & BaseProviderOptions)
  | (OAuthAuth & BaseProviderOptions)
  | (GoogleAuthLibraryAuth & BaseProviderOptions)
  | ({ authType?: undefined } & BaseProviderOptions);

/**
 * Gemini API key authentication (supports both AI SDK standard and Gemini-specific auth types)
 */
export interface GeminiApiKeyAuth {
  authType: 'api-key' | 'gemini-api-key';
  apiKey?: string;
}

/**
 * Vertex AI authentication
 */
export interface VertexAIAuth {
  authType: 'vertex-ai';
  vertexAI: {
    projectId: string;
    location: string;
    apiKey?: string;
  };
}

/**
 * OAuth authentication (personal or service account)
 */
export interface OAuthAuth {
  authType: 'oauth' | 'oauth-personal';
  cacheDir?: string;
}

/**
 * Google Auth Library authentication
 */
export interface GoogleAuthLibraryAuth {
  authType: 'google-auth-library';
  googleAuth?: GoogleAuth;
  googleAuthClient?: unknown; // For backward compatibility
}

/**
 * Logger interface for provider diagnostics and debugging.
 *
 * Supports four log levels:
 * - `debug`: Detailed execution tracing (request/response, tool calls, stream events)
 * - `info`: General execution flow information (session initialization, completion)
 * - `warn`: Warnings about configuration issues or unexpected behavior
 * - `error`: Error messages for failures and exceptions
 *
 * When implementing a custom logger, all four methods must be provided.
 *
 * @example
 * ```typescript
 * const customLogger: Logger = {
 *   debug: (msg) => myLogger.debug(msg),
 *   info: (msg) => myLogger.info(msg),
 *   warn: (msg) => myLogger.warn(msg),
 *   error: (msg) => myLogger.error(msg),
 * };
 * ```
 */
export interface Logger {
  /**
   * Log detailed execution tracing (only shown when verbose mode is enabled).
   * Used for request/response details, tool calls, stream events, and token usage.
   */
  debug(message: string): void;

  /**
   * Log general execution flow information (only shown when verbose mode is enabled).
   * Used for session initialization, request completion, and major state transitions.
   */
  info(message: string): void;

  /**
   * Log warnings about configuration issues or unexpected behavior.
   * Always shown regardless of verbose mode setting.
   */
  warn(message: string): void;

  /**
   * Log error messages for failures and exceptions.
   * Always shown regardless of verbose mode setting.
   */
  error(message: string): void;
}


--- src/validation.ts ---
import type { GeminiProviderOptions } from './types';

/**
 * Validates the authentication options for the Gemini provider.
 * Ensures that the provided configuration has valid authentication credentials.
 *
 * @param options - The provider options to validate
 * @returns The validated options
 * @throws Error if authentication configuration is invalid
 */
export function validateAuthOptions(
  options: GeminiProviderOptions = {}
): GeminiProviderOptions {
  // Default to oauth-personal if no authType specified
  const authType = options.authType || 'oauth-personal';

  // Validate based on auth type
  switch (authType) {
    case 'api-key':
    case 'gemini-api-key':
      if (!('apiKey' in options) || !options.apiKey) {
        throw new Error(`API key is required for ${authType} auth type`);
      }
      return { ...options, authType };

    case 'vertex-ai':
      if ('vertexAI' in options && options.vertexAI) {
        if (
          !options.vertexAI.projectId ||
          options.vertexAI.projectId.trim() === ''
        ) {
          throw new Error('Project ID is required for vertex-ai auth type');
        }
        if (
          !options.vertexAI.location ||
          options.vertexAI.location.trim() === ''
        ) {
          throw new Error('Location is required for vertex-ai auth type');
        }
      } else {
        throw new Error(
          'Vertex AI configuration is required for vertex-ai auth type'
        );
      }
      return { ...options, authType };

    case 'oauth':
    case 'oauth-personal':
      // No additional validation needed for oauth
      return { ...options, authType };

    case 'google-auth-library':
      if (!('googleAuth' in options) || !options.googleAuth) {
        throw new Error(
          'Google Auth Library instance is required for google-auth-library auth type'
        );
      }
      return { ...options, authType };

    default:
      throw new Error(`Invalid auth type: ${String(authType)}`);
  }
}


## Links discovered
- [ai-sdk-v5/GUIDE.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/ai-sdk-v5/GUIDE.md)
- [ai-sdk-v5/BREAKING_CHANGES.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/ai-sdk-v5/BREAKING_CHANGES.md)
- [ai-sdk-v5/TROUBLESHOOTING.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/ai-sdk-v5/TROUBLESHOOTING.md)
- [Project Structure](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/project-structure.md)
- [Authentication Options](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/gemini-cli-auth-options.md)
- [Language Model V2 Implementation](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/language-model-v2-implementation.md)
- [Tool Schema Mapping](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/tool-schema-mapping.md)
- [Zod to Gemini Mapping](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/zod-to-gemini-mapping.md)
- [examples directory](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/examples.md)
- [ai-sdk-v5/GUIDE.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/GUIDE.md)
- [ai-sdk-v5/BREAKING_CHANGES.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/BREAKING_CHANGES.md)
- [ai-sdk-v5/TROUBLESHOOTING.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/TROUBLESHOOTING.md)
- [Project Structure](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/./project-structure.md)
- [Authentication Options](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/./gemini-cli-auth-options.md)
- [Language Model V2 Implementation](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/./language-model-v2-implementation.md)
- [Tool Schema Mapping](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/./tool-schema-mapping.md)
- [Zod to Gemini Mapping](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/./zod-to-gemini-mapping.md)
- [examples directory](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/../examples/)
- [examples](https://github.com/AcidicSoil/llms-txt-registry/blob/main/examples.md)
- [GUIDE.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/GUIDE.md)
- [TROUBLESHOOTING.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/TROUBLESHOOTING.md)
- [examples](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/../../examples/)
- [GUIDE.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/./GUIDE.md)
- [TROUBLESHOOTING.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/./TROUBLESHOOTING.md)
- [BREAKING_CHANGES.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/BREAKING_CHANGES.md)
- [BREAKING_CHANGES.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/docs/ai-sdk-v5/./BREAKING_CHANGES.md)
- [README](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/README.md)
- [README](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main/examples/../README.md)
- [Vercel AI SDK](https://sdk.vercel.ai/docs)
- [@google/gemini-cli-core](https://www.npmjs.com/package/@google/gemini-cli-core)
- [`ai-sdk-v4`](https://github.com/ben-vargas/ai-sdk-provider-gemini-cli/tree/ai-sdk-v4)
- [Google's Terms of Service](https://policies.google.com/terms)
- [CHANGELOG.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/CHANGELOG.md)
- [Examples](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/examples.md)
- [API Reference](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/docs.md)
- [Authentication Guide](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/docs/gemini-cli-auth-options.md)
- [Migration Guide](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/CHANGELOG.md)
- [examples README](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/examples/README.md)
- [Google AI Studio](https://aistudio.google.com/apikey)
- [Contributing Guide](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/CONTRIBUTING.md)
- [LICENSE](https://github.com/AcidicSoil/llms-txt-registry/blob/main/docs/ai-sdk-provider-gemini-cli/LICENSE.md)
- [CHANGELOG.md](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//CHANGELOG.md)
- [Examples](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//examples/)
- [API Reference](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//docs/)
- [Authentication Guide](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//docs/gemini-cli-auth-options.md)
- [Migration Guide](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//CHANGELOG.md)
- [examples README](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//examples/README.md)
- [Contributing Guide](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//CONTRIBUTING.md)
- [LICENSE](https://raw.githubusercontent.com/ben-vargas/ai-sdk-provider-gemini-cli/main//LICENSE)
- [<img src="https://img.shields.io/npm/v/ai-sdk-provider-gemini-cli?color=00A79E" alt="npm stable version" />](https://www.npmjs.com/package/ai-sdk-provider-gemini-cli)
- [<img src="https://img.shields.io/npm/unpacked-size/ai-sdk-provider-gemini-cli?color=00A79E" alt="install size" />](https://www.npmjs.com/package/ai-sdk-provider-gemini-cli)
- [<img src="https://img.shields.io/npm/dy/ai-sdk-provider-gemini-cli.svg?color=00A79E" alt="npm downloads" />](https://www.npmjs.com/package/ai-sdk-provider-gemini-cli)
- [<img src="https://img.shields.io/badge/node-%3E%3D20-00A79E" alt="Node.js ≥ 20" />](https://nodejs.org/en/about/releases/)
- [<img src="https://img.shields.io/npm/l/ai-sdk-provider-gemini-cli?color=00A79E" alt="License: MIT" />](https://www.npmjs.com/package/ai-sdk-provider-gemini-cli)
- [<img src="https://awesome.re/mentioned-badge.svg" alt="Mentioned in Awesome Gemini CLI" />](https://github.com/Piebald-AI/awesome-gemini-cli)

--- .taskmaster/templates/example_prd.txt ---
<context>
# Overview  
[Provide a high-level overview of your product here. Explain what problem it solves, who it's for, and why it's valuable.]

# Core Features  
[List and describe the main features of your product. For each feature, include:
- What it does
- Why it's important
- How it works at a high level]

# User Experience  
[Describe the user journey and experience. Include:
- User personas
- Key user flows
- UI/UX considerations]
</context>
<PRD>
# Technical Architecture  
[Outline the technical implementation details:
- System components
- Data models
- APIs and integrations
- Infrastructure requirements]

# Development Roadmap  
[Break down the development process into phases:
- MVP requirements
- Future enhancements
- Do not think about timelines whatsoever -- all that matters is scope and detailing exactly what needs to be build in each phase so it can later be cut up into tasks]

# Logical Dependency Chain
[Define the logical order of development:
- Which features need to be built first (foundation)
- Getting as quickly as possible to something usable/visible front end that works
- Properly pacing and scoping each feature so it is atomic but can also be built upon and improved as development approaches]

# Risks and Mitigations  
[Identify potential risks and how they'll be addressed:
- Technical challenges
- Figuring out the MVP that we can build upon
- Resource constraints]

# Appendix  
[Include any additional information:
- Research findings
- Technical specifications]
</PRD>

--- .taskmaster/templates/example_prd_rpg.txt ---
<rpg-method>
# Repository Planning Graph (RPG) Method - PRD Template

This template teaches you (AI or human) how to create structured, dependency-aware PRDs using the RPG methodology from Microsoft Research. The key insight: separate WHAT (functional) from HOW (structural), then connect them with explicit dependencies.

## Core Principles

1. **Dual-Semantics**: Think functional (capabilities) AND structural (code organization) separately, then map them
2. **Explicit Dependencies**: Never assume - always state what depends on what
3. **Topological Order**: Build foundation first, then layers on top
4. **Progressive Refinement**: Start broad, refine iteratively

## How to Use This Template

- Follow the instructions in each `<instruction>` block
- Look at `<example>` blocks to see good vs bad patterns
- Fill in the content sections with your project details
- The AI reading this will learn the RPG method by following along
- Task Master will parse the resulting PRD into dependency-aware tasks

## Recommended Tools for Creating PRDs

When using this template to **create** a PRD (not parse it), use **code-context-aware AI assistants** for best results:

**Why?** The AI needs to understand your existing codebase to make good architectural decisions about modules, dependencies, and integration points.

**Recommended tools:**
- **Claude Code** (claude-code CLI) - Best for structured reasoning and large contexts
- **Cursor/Windsurf** - IDE integration with full codebase context
- **Gemini CLI** (gemini-cli) - Massive context window for large codebases
- **Codex/Grok CLI** - Strong code generation with context awareness

**Note:** Once your PRD is created, `task-master parse-prd` works with any configured AI model - it just needs to read the PRD text itself, not your codebase.
</rpg-method>

---

<overview>
<instruction>
Start with the problem, not the solution. Be specific about:
- What pain point exists?
- Who experiences it?
- Why existing solutions don't work?
- What success looks like (measurable outcomes)?

Keep this section focused - don't jump into implementation details yet.
</instruction>

## Problem Statement
[Describe the core problem. Be concrete about user pain points.]

## Target Users
[Define personas, their workflows, and what they're trying to achieve.]

## Success Metrics
[Quantifiable outcomes. Examples: "80% task completion via autopilot", "< 5% manual intervention rate"]

</overview>

---

<functional-decomposition>
<instruction>
Now think about CAPABILITIES (what the system DOES), not code structure yet.

Step 1: Identify high-level capability domains
- Think: "What major things does this system do?"
- Examples: Data Management, Core Processing, Presentation Layer

Step 2: For each capability, enumerate specific features
- Use explore-exploit strategy:
  * Exploit: What features are REQUIRED for core value?
  * Explore: What features make this domain COMPLETE?

Step 3: For each feature, define:
- Description: What it does in one sentence
- Inputs: What data/context it needs
- Outputs: What it produces/returns
- Behavior: Key logic or transformations

<example type="good">
Capability: Data Validation
  Feature: Schema validation
    - Description: Validate JSON payloads against defined schemas
    - Inputs: JSON object, schema definition
    - Outputs: Validation result (pass/fail) + error details
    - Behavior: Iterate fields, check types, enforce constraints

  Feature: Business rule validation
    - Description: Apply domain-specific validation rules
    - Inputs: Validated data object, rule set
    - Outputs: Boolean + list of violated rules
    - Behavior: Execute rules sequentially, short-circuit on failure
</example>

<example type="bad">
Capability: validation.js
  (Problem: This is a FILE, not a CAPABILITY. Mixing structure into functional thinking.)

Capability: Validation
  Feature: Make sure data is good
  (Problem: Too vague. No inputs/outputs. Not actionable.)
</example>
</instruction>

## Capability Tree

### Capability: [Name]
[Brief description of what this capability domain covers]

#### Feature: [Name]
- **Description**: [One sentence]
- **Inputs**: [What it needs]
- **Outputs**: [What it produces]
- **Behavior**: [Key logic]

#### Feature: [Name]
- **Description**:
- **Inputs**:
- **Outputs**:
- **Behavior**:

### Capability: [Name]
...

</functional-decomposition>

---

<structural-decomposition>
<instruction>
NOW think about code organization. Map capabilities to actual file/folder structure.

Rules:
1. Each capability maps to a module (folder or file)
2. Features within a capability map to functions/classes
3. Use clear module boundaries - each module has ONE responsibility
4. Define what each module exports (public interface)

The goal: Create a clear mapping between "what it does" (functional) and "where it lives" (structural).

<example type="good">
Capability: Data Validation
  → Maps to: src/validation/
    ├── schema-validator.js      (Schema validation feature)
    ├── rule-validator.js         (Business rule validation feature)
    └── index.js                  (Public exports)

Exports:
  - validateSchema(data, schema)
  - validateRules(data, rules)
</example>

<example type="bad">
Capability: Data Validation
  → Maps to: src/utils.js
  (Problem: "utils" is not a clear module boundary. Where do I find validation logic?)

Capability: Data Validation
  → Maps to: src/validation/everything.js
  (Problem: One giant file. Features should map to separate files for maintainability.)
</example>
</instruction>

## Repository Structure

```
project-root/
├── src/
│   ├── [module-name]/       # Maps to: [Capability Name]
│   │   ├── [file].js        # Maps to: [Feature Name]
│   │   └── index.js         # Public exports
│   └── [module-name]/
├── tests/
└── docs/
```

## Module Definitions

### Module: [Name]
- **Maps to capability**: [Capability from functional decomposition]
- **Responsibility**: [Single clear purpose]
- **File structure**:
  ```
  module-name/
  ├── feature1.js
  ├── feature2.js
  └── index.js
  ```
- **Exports**:
  - `functionName()` - [what it does]
  - `ClassName` - [what it does]

</structural-decomposition>

---

<dependency-graph>
<instruction>
This is THE CRITICAL SECTION for Task Master parsing.

Define explicit dependencies between modules. This creates the topological order for task execution.

Rules:
1. List modules in dependency order (foundation first)
2. For each module, state what it depends on
3. Foundation modules should have NO dependencies
4. Every non-foundation module should depend on at least one other module
5. Think: "What must EXIST before I can build this module?"

<example type="good">
Foundation Layer (no dependencies):
  - error-handling: No dependencies
  - config-manager: No dependencies
  - base-types: No dependencies

Data Layer:
  - schema-validator: Depends on [base-types, error-handling]
  - data-ingestion: Depends on [schema-validator, config-manager]

Core Layer:
  - algorithm-engine: Depends on [base-types, error-handling]
  - pipeline-orchestrator: Depends on [algorithm-engine, data-ingestion]
</example>

<example type="bad">
- validation: Depends on API
- API: Depends on validation
(Problem: Circular dependency. This will cause build/runtime issues.)

- user-auth: Depends on everything
(Problem: Too many dependencies. Should be more focused.)
</example>
</instruction>

## Dependency Chain

### Foundation Layer (Phase 0)
No dependencies - these are built first.

- **[Module Name]**: [What it provides]
- **[Module Name]**: [What it provides]

### [Layer Name] (Phase 1)
- **[Module Name]**: Depends on [[module-from-phase-0], [module-from-phase-0]]
- **[Module Name]**: Depends on [[module-from-phase-0]]

### [Layer Name] (Phase 2)
- **[Module Name]**: Depends on [[module-from-phase-1], [module-from-foundation]]

[Continue building up layers...]

</dependency-graph>

---

<implementation-roadmap>
<instruction>
Turn the dependency graph into concrete development phases.

Each phase should:
1. Have clear entry criteria (what must exist before starting)
2. Contain tasks that can be parallelized (no inter-dependencies within phase)
3. Have clear exit criteria (how do we know phase is complete?)
4. Build toward something USABLE (not just infrastructure)

Phase ordering follows topological sort of dependency graph.

<example type="good">
Phase 0: Foundation
  Entry: Clean repository
  Tasks:
    - Implement error handling utilities
    - Create base type definitions
    - Setup configuration system
  Exit: Other modules can import foundation without errors

Phase 1: Data Layer
  Entry: Phase 0 complete
  Tasks:
    - Implement schema validator (uses: base types, error handling)
    - Build data ingestion pipeline (uses: validator, config)
  Exit: End-to-end data flow from input to validated output
</example>

<example type="bad">
Phase 1: Build Everything
  Tasks:
    - API
    - Database
    - UI
    - Tests
  (Problem: No clear focus. Too broad. Dependencies not considered.)
</example>
</instruction>

## Development Phases

### Phase 0: [Foundation Name]
**Goal**: [What foundational capability this establishes]

**Entry Criteria**: [What must be true before starting]

**Tasks**:
- [ ] [Task name] (depends on: [none or list])
  - Acceptance criteria: [How we know it's done]
  - Test strategy: [What tests prove it works]

- [ ] [Task name] (depends on: [none or list])

**Exit Criteria**: [Observable outcome that proves phase complete]

**Delivers**: [What can users/developers do after this phase?]

---

### Phase 1: [Layer Name]
**Goal**:

**Entry Criteria**: Phase 0 complete

**Tasks**:
- [ ] [Task name] (depends on: [[tasks-from-phase-0]])
- [ ] [Task name] (depends on: [[tasks-from-phase-0]])

**Exit Criteria**:

**Delivers**:

---

[Continue with more phases...]

</implementation-roadmap>

---

<test-strategy>
<instruction>
Define how testing will be integrated throughout development (TDD approach).

Specify:
1. Test pyramid ratios (unit vs integration vs e2e)
2. Coverage requirements
3. Critical test scenarios
4. Test generation guidelines for Surgical Test Generator

This section guides the AI when generating tests during the RED phase of TDD.

<example type="good">
Critical Test Scenarios for Data Validation module:
  - Happy path: Valid data passes all checks
  - Edge cases: Empty strings, null values, boundary numbers
  - Error cases: Invalid types, missing required fields
  - Integration: Validator works with ingestion pipeline
</example>
</instruction>

## Test Pyramid

```
        /\
       /E2E\       ← [X]% (End-to-end, slow, comprehensive)
      /------\
     /Integration\ ← [Y]% (Module interactions)
    /------------\
   /  Unit Tests  \ ← [Z]% (Fast, isolated, deterministic)
  /----------------\
```

## Coverage Requirements
- Line coverage: [X]% minimum
- Branch coverage: [X]% minimum
- Function coverage: [X]% minimum
- Statement coverage: [X]% minimum

## Critical Test Scenarios

### [Module/Feature Name]
**Happy path**:
- [Scenario description]
- Expected: [What should happen]

**Edge cases**:
- [Scenario description]
- Expected: [What should happen]

**Error cases**:
- [Scenario description]
- Expected: [How system handles failure]

**Integration points**:
- [What interactions to test]
- Expected: [End-to-end behavior]

## Test Generation Guidelines
[Specific instructions for Surgical Test Generator about what to focus on, what patterns to follow, project-specific test conventions]

</test-strategy>

---

<architecture>
<instruction>
Describe technical architecture, data models, and key design decisions.

Keep this section AFTER functional/structural decomposition - implementation details come after understanding structure.
</instruction>

## System Components
[Major architectural pieces and their responsibilities]

## Data Models
[Core data structures, schemas, database design]

## Technology Stack
[Languages, frameworks, key libraries]

**Decision: [Technology/Pattern]**
- **Rationale**: [Why chosen]
- **Trade-offs**: [What we're giving up]
- **Alternatives considered**: [What else we looked at]

</architecture>

---

<risks>
<instruction>
Identify risks that could derail development and how to mitigate them.

Categories:
- Technical risks (complexity, unknowns)
- Dependency risks (blocking issues)
- Scope risks (creep, underestimation)
</instruction>

## Technical Risks
**Risk**: [Description]
- **Impact**: [High/Medium/Low - effect on project]
- **Likelihood**: [High/Medium/Low]
- **Mitigation**: [How to address]
- **Fallback**: [Plan B if mitigation fails]

## Dependency Risks
[External dependencies, blocking issues]

## Scope Risks
[Scope creep, underestimation, unclear requirements]

</risks>

---

<appendix>
## References
[Papers, documentation, similar systems]

## Glossary
[Domain-specific terms]

## Open Questions
[Things to resolve during development]
</appendix>

---

<task-master-integration>
# How Task Master Uses This PRD

When you run `task-master parse-prd <file>.txt`, the parser:

1. **Extracts capabilities** → Main tasks
   - Each `### Capability:` becomes a top-level task

2. **Extracts features** → Subtasks
   - Each `#### Feature:` becomes a subtask under its capability

3. **Parses dependencies** → Task dependencies
   - `Depends on: [X, Y]` sets task.dependencies = ["X", "Y"]

4. **Orders by phases** → Task priorities
   - Phase 0 tasks = highest priority
   - Phase N tasks = lower priority, properly sequenced

5. **Uses test strategy** → Test generation context
   - Feeds test scenarios to Surgical Test Generator during implementation

**Result**: A dependency-aware task graph that can be executed in topological order.

## Why RPG Structure Matters

Traditional flat PRDs lead to:
- ❌ Unclear task dependencies
- ❌ Arbitrary task ordering
- ❌ Circular dependencies discovered late
- ❌ Poorly scoped tasks

RPG-structured PRDs provide:
- ✅ Explicit dependency chains
- ✅ Topological execution order
- ✅ Clear module boundaries
- ✅ Validated task graph before implementation

## Tips for Best Results

1. **Spend time on dependency graph** - This is the most valuable section for Task Master
2. **Keep features atomic** - Each feature should be independently testable
3. **Progressive refinement** - Start broad, use `task-master expand` to break down complex tasks
4. **Use research mode** - `task-master parse-prd --research` leverages AI for better task generation
</task-master-integration>


--- README.md ---
# llms-txt Registry

A centralized registry of generated `llms.txt` documentation artifacts for various libraries and tools.

## Usage

This registry is designed to be consumed via **MCP** (Model Context Protocol) using `gitmcp` as the gateway.

### Gemini CLI Configuration

Add this to your `~/.gemini/settings.json` (or project config):

```json
{
  "mcpServers": {
    "llms-registry": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "gitmcp.io/<org>/<repo>"
      ]
    }
  }
}
```

Replace `<org>/<repo>` with the GitHub path to this repository.

### Codex Configuration

For Codex or other clients supporting `mcp-remote`:

```toml
[mcpServers.llms-registry]
command = "npx"
args = ["-y", "mcp-remote", "gitmcp.io/<org>/<repo>"]
```

## Structure

Artifacts are stored in the `docs/` directory, organized by source ID:

- `docs/<source-id>/<source-id>-llms.txt`
- `docs/<source-id>/metadata.json`

Example:
- `docs/tanstack-router/tanstack-router-llms.txt`

A machine-readable index is available at `docs/index.json`.

## Contributor Guide (Local-First Workflow)

We use a local-first workflow to generate artifacts using your own LM Studio instance. This avoids sending code to remote APIs during CI.

### Prerequisites

- Python 3.10+
- [LM Studio](https://lmstudio.ai/) (with a model loaded and Server started)
- [lmstudio-lmstxt-generator](https://pypi.org/project/lmstudio-lmstxt-generator/) CLI tool (installed via `pip install lmstudio-lmstxt-generator` or similar)

### Setup

1. Install dependencies:
   ```bash
   pip install -r requirements.txt
   ```

2. Install Git hooks (Critical):
   ```bash
   ./scripts/setup_hooks.sh
   ```
   This installs a `pre-push` hook that ensures `docs/` are in sync with `sources.json`.

### Adding a Source

1. Edit `sources.json` to add a new entry:
   ```json
   {
     "id": "my-lib",
     "url": "https://github.com/owner/lib",
     "type": "repo",
     "profile": "default"
   }
   ```

2. Run the refresh script locally:
   ```bash
   # Make sure LM Studio server is running at http://localhost:1234
   python scripts/refresh.py --only my-lib
   ```

3. Verify the output in `docs/my-lib/`.

4. Commit and push.

### Refreshing All Sources

To update all artifacts:
```bash
python scripts/refresh.py
```

## Links discovered
- [LM Studio](https://lmstudio.ai/)
- [lmstudio-lmstxt-generator](https://pypi.org/project/lmstudio-lmstxt-generator/)

--- codefetch/src.md ---
<filetree>
Project Structure:
├── .github
│   └── workflows
│       └── lint.yml
├── scripts
│   ├── refresh.py
│   └── setup_hooks.sh
├── src
│   ├── artifact_ingest
│   │   ├── __init__.py
│   │   ├── index.py
│   │   └── ingest.py
│   ├── generator_runner
│   │   ├── __init__.py
│   │   └── runner.py
│   ├── git_sync
│   │   ├── __init__.py
│   │   └── status.py
│   ├── registry_config
│   │   ├── __init__.py
│   │   ├── loader.py
│   │   └── models.py
│   ├── reporting
│   │   ├── __init__.py
│   │   └── report.py
│   └── __init__.py
├── tests
│   ├── test_index.py
│   ├── test_ingest.py
│   ├── test_registry_config.py
│   ├── test_reporting.py
│   └── test_runner.py
├── GitMCP-usage-example.md
├── package.json
├── requirements.txt
└── sources.json

</filetree>

<source_code>
GitMCP-usage-example.md
```
---

### gitmcp Server usage

for ANY work or questions relating to <libraries> and/or providers use the <name>_docs server for that repo:

- call search_<name>_documentation to find relevant docs
- call fetch_<name>_documentation to read the primary docs
- reflect on the input question
- call fetch_url_content for any external URLs referenced
- call search_<name>_code if code locations are referenced or needed
- use this to answer the question

---
```

package.json
```
{
  "name": "llms-txt-registry",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "code": "codefetch -t 5 -o src.md --exclude-dir docs,.gemini,.cursor,.pytest_cache,.clinerules,.taskmaster --max-tokens 5000 --token-limiter truncated",
    "code:gem": "codefetch -t 5 --include-dir .gemini/commands/tm -o gemini-commands.md"
  },
  "devDependencies": {
    "codefetch": "^2.2.0"
  },
  "packageManager": "pnpm@10.25.0"
}
```

requirements.txt
```
pydantic>=2.0
```

sources.json
```
{
  "version": "1.0",
  "profiles": {
    "default": {
      "name": "default",
      "timeout": 300
    }
  },
  "sources": [
    {
      "id": "tanstack-router",
      "url": "https://github.com/tanstack/router",
      "type": "repo",
      "profile": "default"
    },
    {
      "id": "supabase",
      "url": "https://github.com/supabase/supabase",
      "type": "repo",
      "profile": "default"
    }
  ]
}
```

scripts/refresh.py
```
#!/usr/bin/env python3
import argparse
import sys
import os
import uuid
import logging
from pathlib import Path

# Ensure src is in path
sys.path.append(str(Path(__file__).parent.parent))

from src.registry_config.loader import load_manifest, save_manifest
from src.reporting.report import RunReport, SourceResult
from src.generator_runner.runner import GeneratorRunner
from src.artifact_ingest.ingest import ingest_artifacts
from src.artifact_ingest.index import generate_registry_index
from src.git_sync.status import check_stale_artifacts

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logger = logging.getLogger("refresh")

def main():
    parser = argparse.ArgumentParser(description="Refresh the llms-txt registry artifacts.")
    parser.add_argument("--manifest", default="sources.json", help="Path to sources.json")
    parser.add_argument("--docs-root", default="docs", help="Path to docs directory")
    parser.add_argument("--only", help="Only refresh a specific source ID")
    parser.add_argument("--api-base", default="http://localhost:1234/v1", help="LM Studio API base")
    parser.add_argument("--check", action="store_true", help="Only check for stale artifacts and exit")
    
    args = parser.parse_args()

    if args.check:
[TRUNCATED]
```

scripts/setup_hooks.sh
```
#!/bin/bash
HOOK_FILE=".git/hooks/pre-push"

echo "Installing pre-push hook..."

cat <<EOF > "$HOOK_FILE"
#!/bin/bash
# Task Master Registry - Pre-push Hook
# Validates that artifacts are updated if sources.json changed.

python3 scripts/refresh.py --check
RESULT=\$?

if [ \$RESULT -ne 0 ]; then
  echo "Error: Stale artifacts detected. Please run 'scripts/refresh.py' and commit the changes before pushing."
  exit 1
fi

exit 0
EOF

chmod +x "$HOOK_FILE"
echo "Hook installed successfully at $HOOK_FILE"
```

src/__init__.py
```
```

tests/test_index.py
```
import json
from pathlib import Path
from src.artifact_ingest.index import generate_registry_index

def test_generate_index(tmp_path):
    docs_root = tmp_path / "docs"
    docs_root.mkdir()
    
    # Source 1 with artifacts and metadata
    src1 = docs_root / "src1"
    src1.mkdir()
    (src1 / "llms.txt").touch()
    (src1 / "metadata.json").write_text(json.dumps({"model_used": "gpt-4"}))
    
    # Source 2 with artifacts only
    src2 = docs_root / "src2"
    src2.mkdir()
    (src2 / "foo-llms.txt").touch()
    
    # Source 3 empty
    (docs_root / "src3").mkdir()
    
    index = generate_registry_index(docs_root)
    
    assert len(index) == 2
    
    s1 = next(s for s in index if s["id"] == "src1")
    assert s1["model_used"] == "gpt-4"
    assert "src1/llms.txt" in s1["artifacts"]
    
    s2 = next(s for s in index if s["id"] == "src2")
    assert "src2/foo-llms.txt" in s2["artifacts"]
    assert "model_used" not in s2
    
    assert (docs_root / "index.json").exists()
```

tests/test_ingest.py
```
import json
from pathlib import Path
from src.artifact_ingest.ingest import ingest_artifacts

def test_ingest_artifacts(tmp_path):
    # Setup source dir
    temp_dir = tmp_path / "temp"
    temp_dir.mkdir()
    artifact = temp_dir / "foo-llms.txt"
    artifact.write_bytes(b"Hello\r\nWorld") # CRLF
    
    docs_root = tmp_path / "docs"
    docs_root.mkdir()
    
    ingested = ingest_artifacts("test-src", temp_dir, docs_root, model_used="gpt-4")
    
    # Check location
    target_file = docs_root / "test-src" / "foo-llms.txt"
    assert target_file.exists()
    assert str(target_file.relative_to(docs_root)) in ingested
    
    # Check normalization
    assert target_file.read_bytes() == b"Hello\nWorld"
    
    # Check metadata
    meta_file = docs_root / "test-src" / "metadata.json"
    assert meta_file.exists()
    meta = json.loads(meta_file.read_text())
    assert meta["source_id"] == "test-src"
    assert meta["model_used"] == "gpt-4"
    assert len(meta["artifacts"]) == 1
```

tests/test_registry_config.py
```
import pytest
from pathlib import Path
import json
from src.registry_config.models import Manifest, Source, GeneratorProfile
from src.registry_config.loader import load_manifest
from pydantic import ValidationError

def test_source_validation_slug():
    # Valid slug
    s = Source(id="valid-slug", url="https://example.com")
    assert s.id == "valid-slug"

    # Invalid slug (uppercase)
    with pytest.raises(ValidationError):
        Source(id="Invalid-Slug", url="https://example.com")

    # Invalid slug (spaces)
    with pytest.raises(ValidationError):
        Source(id="invalid slug", url="https://example.com")

def test_manifest_duplicate_ids():
    sources = [
        Source(id="s1", url="https://a.com"),
        Source(id="s1", url="https://b.com")
    ]
    with pytest.raises(ValidationError) as exc:
        Manifest(sources=sources)
    assert "Duplicate source IDs" in str(exc.value)

def test_load_manifest(tmp_path):
    manifest_file = tmp_path / "sources.json"
    data = {
        "version": "1.0",
        "profiles": {
            "default": {"name": "default", "timeout": 60}
        },
        "sources": [
            {"id": "test-source", "url": "https://example.com", "profile": "default"}
        ]
    }
    manifest_file.write_text(json.dumps(data))
    
    manifest = load_manifest(str(manifest_file))
    assert len(manifest.sources) == 1
    assert manifest.sources[0].id == "test-source"
[TRUNCATED]
```

tests/test_reporting.py
```
import json
from pathlib import Path
from src.reporting.report import RunReport, SourceResult

def test_report_lifecycle():
    report = RunReport(run_id="test-run")
    assert report.start_time > 0
    assert report.end_time is None
    
    result = SourceResult(id="s1", status="success", duration=1.5)
    report.record_result(result)
    
    assert report.summary["success"] == 1
    assert report.results["s1"] == result
    
    report.finalize()
    assert report.end_time >= report.start_time

def test_atomic_write(tmp_path):
    report_file = tmp_path / "report.json"
    report = RunReport(run_id="test-run")
    report.record_result(SourceResult(id="s1", status="success", duration=1.0))
    report.finalize()
    
    report.to_json(str(report_file))
    
    assert report_file.exists()
    content = json.loads(report_file.read_text())
    assert content["run_id"] == "test-run"
    assert content["summary"]["success"] == 1
```

tests/test_runner.py
```
import subprocess
import pytest
from unittest.mock import patch, MagicMock
from pathlib import Path
from src.generator_runner.runner import GeneratorRunner
from src.registry_config.models import Source

@patch("subprocess.run")
def test_runner_success(mock_run, tmp_path):
    runner = GeneratorRunner(output_root=tmp_path)
    source = Source(id="test-src", url="https://github.com/test/repo")
    
    # Mock successful result
    mock_result = MagicMock()
    mock_result.returncode = 0
    mock_result.stdout = "Success"
    mock_run.return_value = mock_result
    
    # Create fake artifact
    (tmp_path / "temp_work" / "test-src").mkdir(parents=True)
    (tmp_path / "temp_work" / "test-src" / "test-llms.txt").touch()
    
    result = runner.run_source(source)
    
    assert result["status"] == "success"
    assert "test-llms.txt" in result["artifacts"][0]
    
    # Verify command structure
    args, kwargs = mock_run.call_args
    cmd = args[0]
    assert cmd[0] == "lmstudio-lmstxt"
    assert cmd[1] == "https://github.com/test/repo"
    assert "--output-dir" in cmd
    assert "--stamp" in cmd

@patch("subprocess.run")
def test_runner_failure(mock_run, tmp_path):
    runner = GeneratorRunner(output_root=tmp_path)
[TRUNCATED]
```

.github/workflows/lint.yml
```
name: Lint and Validate

on:
  push:
    branches: [ main, master ]
  pull_request:
    branches: [ main, master ]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.10'

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install pytest

    - name: Run Unit Tests
      run: |
        PYTHONPATH=. pytest tests/

    - name: Validate sources.json
      run: |
        python3 -c "from src.registry_config.loader import load_manifest; load_manifest('sources.json'); print('Manifest valid.')"

    - name: Verify Directory Structure
      run: |
        if [ ! -d "docs" ]; then echo "docs/ directory missing"; exit 1; fi
        if [ ! -d "src" ]; then echo "src/ directory missing"; exit 1; fi
        if [ ! -f "scripts/refresh.py" ]; then echo "refresh script missing"; exit 1; fi
```

src/artifact_ingest/__init__.py
```
```

src/artifact_ingest/index.py
```
import json
from pathlib import Path
from typing import List, Dict, Optional

def generate_registry_index(docs_root: Path, output_path: Path = None):
    """
    Scan docs_root for sources and generate an index.json.
    """
    index = []
    
    # Iterate over directories in docs_root
    for source_dir in docs_root.iterdir():
        if not source_dir.is_dir():
            continue
            
        metadata_path = source_dir / "metadata.json"
        source_data = {
            "id": source_dir.name,
            "artifacts": []
        }
        
        # Try to load metadata if it exists
        if metadata_path.exists():
            try:
                meta = json.loads(metadata_path.read_text(encoding="utf-8"))
                source_data["last_refreshed"] = meta.get("last_refreshed")
                source_data["model_used"] = meta.get("model_used")
                source_data["description"] = meta.get("description") # If we added this to metadata
            except Exception:
                pass
        
        # Scan for artifacts directly to be sure
        # Match llms.txt, project-llms.txt, project-llms-ctx.txt, etc.
        patterns = ["llms.txt", "*-llms*.txt"]
        found_artifacts = set()
        
        for pattern in patterns:
            for artifact in source_dir.glob(pattern):
                found_artifacts.add(str(artifact.relative_to(docs_root)))
        
        source_data["artifacts"] = sorted(list(found_artifacts))
            
[TRUNCATED]
```

src/artifact_ingest/ingest.py
```
import shutil
import json
import time
from pathlib import Path
from typing import List, Dict, Optional

def normalize_line_endings(path: Path):
    """Ensure file uses LF line endings."""
    content = path.read_bytes()
    normalized = content.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
    path.write_bytes(normalized)

def ingest_artifacts(
    source_id: str, 
    temp_dir: Path, 
    docs_root: Path, 
    model_used: Optional[str] = None
) -> List[str]:
    """Normalize and move artifacts to docs/<id>/, and generate metadata."""
    target_dir = docs_root / source_id
    target_dir.mkdir(parents=True, exist_ok=True)
    
    ingested_files = []
    
    # Find all *-llms*.txt files in the temp dir
    for artifact in temp_dir.glob("**/*-llms*.txt"):
        # Normalize and copy
        normalize_line_endings(artifact)
        
        target_path = target_dir / artifact.name
        shutil.copy2(artifact, target_path)
        ingested_files.append(str(target_path.relative_to(docs_root)))

    # Generate metadata.json
    metadata = {
        "source_id": source_id,
        "last_refreshed": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
        "model_used": model_used or "unknown",
        "artifacts": ingested_files
    }
    
[TRUNCATED]
```

src/generator_runner/__init__.py
```
```

src/generator_runner/runner.py
```
import subprocess
import os
import time
import logging
from pathlib import Path
from typing import Optional, List, Dict
from ..registry_config.models import Source, GeneratorProfile

logger = logging.getLogger(__name__)

class GeneratorRunner:
    def __init__(self, output_root: Path, api_base: str = "http://localhost:1234/v1"):
        self.output_root = output_root
        self.api_base = api_base

    def run_source(self, source: Source, profile: Optional[GeneratorProfile] = None) -> Dict[str, any]:
        """Execute the lmstudio-lmstxt CLI for a single source."""
        start_time = time.time()
        
        # Determine model
        model = source.last_model_used or (profile.model if profile else None)
        
        # Prepare command
        cmd = ["lmstudio-lmstxt", str(source.url)]
        
        # We target a temporary output directory inside the root for isolation
        temp_out = self.output_root / "temp_work" / source.id
        temp_out.mkdir(parents=True, exist_ok=True)
        
        cmd.extend(["--output-dir", str(temp_out)])
        
        if model:
            cmd.extend(["--model", model])
        
        if self.api_base:
            cmd.extend(["--api-base", self.api_base])
            
        cmd.append("--stamp") # Always stamp for registry
        
        # Set environment variables (e.g. for CTX generation)
        env = os.environ.copy()
[TRUNCATED]
```

src/git_sync/__init__.py
```
```

src/git_sync/status.py
```
import subprocess
import logging
from pathlib import Path
from typing import List

logger = logging.getLogger(__name__)

def get_staged_changes() -> List[str]:
    """Return a list of staged files using git status --porcelain."""
    try:
        result = subprocess.run(
            ["git", "status", "--porcelain"],
            capture_output=True,
            text=True,
            check=True
        )
        return [line[3:].strip() for line in result.stdout.splitlines() if line.startswith("M ") or line.startswith("A ")]
    except Exception as e:
        logger.error(f"Failed to get git status: {e}")
        return []

def check_stale_artifacts(manifest_path: str = "sources.json", docs_root: str = "docs") -> bool:
    """
    Heuristic check: if sources.json is modified but docs/ isn't changed in the same commit,
    it might be stale.
    """
    staged = get_staged_changes()
    if manifest_path in staged:
        # Check if any file in docs/ is also staged
        docs_changes = [f for f in staged if f.startswith(docs_root)]
        if not docs_changes:
            return True # Stale
    return False
```

src/registry_config/__init__.py
```
```

src/registry_config/loader.py
```
import json
from pathlib import Path
from typing import Optional
from .models import Manifest

def load_manifest(path: str = "sources.json") -> Manifest:
    """Load and validate the source manifest."""
    p = Path(path)
    if not p.exists():
        return Manifest()
    
    with p.open("r", encoding="utf-8") as f:
        data = json.load(f)
    
    return Manifest.model_validate(data)

def save_manifest(manifest: Manifest, path: str = "sources.json") -> None:
    """Save the manifest to disk with consistent formatting."""
    with Path(path).open("w", encoding="utf-8") as f:
        json.dump(manifest.model_dump(exclude_none=True), f, indent=2)
        f.write("\n")
```

src/registry_config/models.py
```
from __future__ import annotations
from typing import List, Optional, Dict, Any
from pydantic import BaseModel, Field, field_validator, AnyHttpUrl
import re

SLUG_REGEX = re.compile(r'^[a-z0-9]+(?:-[a-z0-9]+)*$')

class GeneratorProfile(BaseModel):
    name: str = Field(..., description="Unique name of the profile")
    description: Optional[str] = None
    timeout: int = Field(default=300, description="Execution timeout in seconds")
    headers: Dict[str, str] = Field(default_factory=dict, description="Custom headers for the request")
    model: Optional[str] = Field(default=None, description="Preferred model identifier")

class Source(BaseModel):
    id: str = Field(..., description="Unique URL-safe slug ID for the source")
    url: AnyHttpUrl = Field(..., description="URL of the repository or documentation site")
    type: str = Field(default="repo", description="Type of source: 'repo' or 'site'")
    profile: Optional[str] = Field(default=None, description="Name of the generator profile to use")
    enabled: bool = Field(default=True, description="Whether this source should be processed")
    tags: List[str] = Field(default_factory=list, description="Categorization tags")
    
    # Metadata fields (updated during refresh)
[TRUNCATED]
```

src/reporting/__init__.py
```
```

src/reporting/report.py
```
from __future__ import annotations
import json
import time
import os
import tempfile
from dataclasses import dataclass, field, asdict
from pathlib import Path
from typing import List, Dict, Optional

@dataclass
class SourceResult:
    id: str
    status: str  # "success", "failure", "skipped"
    duration: float
    model_used: Optional[str] = None
    error: Optional[str] = None
    artifacts_generated: List[str] = field(default_factory=list)

@dataclass
class RunReport:
    run_id: str
    start_time: float = field(default_factory=time.time)
    end_time: Optional[float] = None
    results: Dict[str, SourceResult] = field(default_factory=dict)
    summary: Dict[str, int] = field(default_factory=lambda: {"success": 0, "failure": 0, "skipped": 0})

    def record_result(self, result: SourceResult):
        self.results[result.id] = result
        self.summary[result.status] += 1

    def finalize(self):
        self.end_time = time.time()

    def to_json(self, path: str = "refresh-report.json"):
        data = asdict(self)
        target_path = Path(path)
        
        # Atomic write: write to temp file then rename
        dir_name = target_path.parent
        with tempfile.NamedTemporaryFile("w", dir=str(dir_name) if dir_name.name else ".", delete=False, encoding="utf-8") as tmp:
            json.dump(data, tmp, indent=2)
[TRUNCATED]
```

</source_code>

--- requirements.txt ---
pydantic>=2.0


--- scripts/refresh.py ---
#!/usr/bin/env python3
import argparse
import sys
import os
import uuid
import logging
from pathlib import Path

# Ensure src is in path
sys.path.append(str(Path(__file__).parent.parent))

from src.registry_config.loader import load_manifest, save_manifest
from src.reporting.report import RunReport, SourceResult
from src.generator_runner.runner import GeneratorRunner
from src.artifact_ingest.ingest import ingest_artifacts
from src.artifact_ingest.index import generate_registry_index
from src.git_sync.status import check_stale_artifacts

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logger = logging.getLogger("refresh")

def main():
    parser = argparse.ArgumentParser(description="Refresh the llms-txt registry artifacts.")
    parser.add_argument("--manifest", default="sources.json", help="Path to sources.json")
    parser.add_argument("--docs-root", default="docs", help="Path to docs directory")
    parser.add_argument("--only", help="Only refresh a specific source ID")
    parser.add_argument("--api-base", default="http://localhost:1234/v1", help="LM Studio API base")
    parser.add_argument("--check", action="store_true", help="Only check for stale artifacts and exit")
    
    args = parser.parse_args()

    if args.check:
        if check_stale_artifacts(args.manifest, args.docs_root):
            logger.warning("STALE: sources.json was modified but no artifacts in docs/ were updated.")
            sys.exit(1)
        else:
            logger.info("VALID: No stale artifacts detected.")
            sys.exit(0)

    manifest = load_manifest(args.manifest)
    if not manifest.sources:
        logger.error("No sources found in manifest.")
        return

    docs_root = Path(args.docs_root)
    runner = GeneratorRunner(output_root=Path(".taskmaster/tmp"), api_base=args.api_base)
    report = RunReport(run_id=str(uuid.uuid4()))

    sources_to_run = manifest.sources
    if args.only:
        sources_to_run = [s for s in sources_to_run if s.id == args.only]
        if not sources_to_run:
            logger.error(f"Source ID '{args.only}' not found in manifest.")
            return

    try:
        for source in sources_to_run:
            if not source.enabled and not args.only:
                logger.info(f"Skipping disabled source: {source.id}")
                report.record_result(SourceResult(id=source.id, status="skipped", duration=0))
                continue

            logger.info(f"Processing source: {source.id} ({source.url})")
            profile = manifest.profiles.get(source.profile) if source.profile else None
            
            gen_result = runner.run_source(source, profile)
            
            if gen_result["status"] == "success":
                ingested = ingest_artifacts(
                    source.id, 
                    gen_result["temp_dir"], 
                    docs_root, 
                    model_used=gen_result.get("model_used")
                )
                
                # Update manifest metadata
                source.last_refreshed = gen_result.get("timestamp") or report.start_time
                source.last_model_used = gen_result.get("model_used")
                
                report.record_result(SourceResult(
                    id=source.id,
                    status="success",
                    duration=gen_result["duration"],
                    model_used=gen_result.get("model_used"),
                    artifacts_generated=ingested
                ))
                logger.info(f"SUCCESS: {source.id} - {len(ingested)} artifacts generated.")
            else:
                report.record_result(SourceResult(
                    id=source.id,
                    status="failure",
                    duration=gen_result["duration"],
                    error=gen_result.get("error")
                ))
                logger.error(f"FAILURE: {source.id} - {gen_result.get('error')}")

    finally:
        report.finalize()
        report.to_json()
        save_manifest(manifest, args.manifest)
        
        # Generate Index
        try:
            generate_registry_index(docs_root)
            logger.info("Registry index generated at docs/index.json")
        except Exception as e:
            logger.error(f"Failed to generate registry index: {e}")
            
        logger.info(f"Run complete. Report saved to refresh-report.json")

if __name__ == "__main__":
    main()

--- .clinerules/cline_rules.md ---
---
description: Guidelines for creating and maintaining Cline rules to ensure consistency and effectiveness.
globs: .cline/rules/*.md
alwaysApply: true
---

- **Required Rule Structure:**
  ```markdown
  ---
  description: Clear, one-line description of what the rule enforces
  globs: path/to/files/*.ext, other/path/**/*
  alwaysApply: boolean
  ---

  - **Main Points in Bold**
    - Sub-points with details
    - Examples and explanations
  ```

- **File References:**
  - Use `[filename](mdc:path/to/file)` ([filename](mdc:filename)) to reference files
  - Example: [prisma.md](.clinerules/prisma.md) for rule references
  - Example: [schema.prisma](mdc:prisma/schema.prisma) for code references

- **Code Examples:**
  - Use language-specific code blocks
  ```typescript
  // ✅ DO: Show good examples
  const goodExample = true;
  
  // ❌ DON'T: Show anti-patterns
  const badExample = false;
  ```

- **Rule Content Guidelines:**
  - Start with high-level overview
  - Include specific, actionable requirements
  - Show examples of correct implementation
  - Reference existing code when possible
  - Keep rules DRY by referencing other rules

- **Rule Maintenance:**
  - Update rules when new patterns emerge
  - Add examples from actual codebase
  - Remove outdated patterns
  - Cross-reference related rules

- **Best Practices:**
  - Use bullet points for clarity
  - Keep descriptions concise
  - Include both DO and DON'T examples
  - Reference actual code over theoretical examples
  - Use consistent formatting across rules 

## Links discovered
- [filename](https://github.com/AcidicSoil/llms-txt-registry/blob/main/.clinerules/mdc:path/to/file.md)
- [filename](https://github.com/AcidicSoil/llms-txt-registry/blob/main/.clinerules/mdc:filename.md)
- [prisma.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/.clinerules/.clinerules/prisma.md)
- [schema.prisma](https://github.com/AcidicSoil/llms-txt-registry/blob/main/.clinerules/mdc:prisma/schema.prisma)

--- .clinerules/dev_workflow.md ---
---
description: Guide for using Taskmaster to manage task-driven development workflows
globs: **/*
alwaysApply: true
---

# Taskmaster Development Workflow

This guide outlines the standard process for using Taskmaster to manage software development projects. It is written as a set of instructions for you, the AI agent.

- **Your Default Stance**: For most projects, the user can work directly within the `master` task context. Your initial actions should operate on this default context unless a clear pattern for multi-context work emerges.
- **Your Goal**: Your role is to elevate the user's workflow by intelligently introducing advanced features like **Tagged Task Lists** when you detect the appropriate context. Do not force tags on the user; suggest them as a helpful solution to a specific need.

## The Basic Loop
The fundamental development cycle you will facilitate is:
1.  **`list`**: Show the user what needs to be done.
2.  **`next`**: Help the user decide what to work on.
3.  **`show <id>`**: Provide details for a specific task.
4.  **`expand <id>`**: Break down a complex task into smaller, manageable subtasks.
5.  **Implement**: The user writes the code and tests.
6.  **`update-subtask`**: Log progress and findings on behalf of the user.
7.  **`set-status`**: Mark tasks and subtasks as `done` as work is completed.
8.  **Repeat**.

All your standard command executions should operate on the user's current task context, which defaults to `master`.

---

## Standard Development Workflow Process

### Simple Workflow (Default Starting Point)

For new projects or when users are getting started, operate within the `master` tag context:

-   Start new projects by running `initialize_project` tool / `task-master init` or `parse_prd` / `task-master parse-prd --input='<prd-file.txt>'` (see @`taskmaster.md`) to generate initial tasks.json with tagged structure
-   Configure rule sets during initialization with `--rules` flag (e.g., `task-master init --rules cline,windsurf`) or manage them later with `task-master rules add/remove` commands  
-   Begin coding sessions with `get_tasks` / `task-master list` (see @`taskmaster.md`) to see current tasks, status, and IDs
-   Determine the next task to work on using `next_task` / `task-master next` (see @`taskmaster.md`)
-   Analyze task complexity with `analyze_project_complexity` / `task-master analyze-complexity --research` (see @`taskmaster.md`) before breaking down tasks
-   Review complexity report using `complexity_report` / `task-master complexity-report` (see @`taskmaster.md`)
-   Select tasks based on dependencies (all marked 'done'), priority level, and ID order
-   View specific task details using `get_task` / `task-master show <id>` (see @`taskmaster.md`) to understand implementation requirements
-   Break down complex tasks using `expand_task` / `task-master expand --id=<id> --force --research` (see @`taskmaster.md`) with appropriate flags like `--force` (to replace existing subtasks) and `--research`
-   Implement code following task details, dependencies, and project standards
-   Mark completed tasks with `set_task_status` / `task-master set-status --id=<id> --status=done` (see @`taskmaster.md`)
-   Update dependent tasks when implementation differs from original plan using `update` / `task-master update --from=<id> --prompt="..."` or `update_task` / `task-master update-task --id=<id> --prompt="..."` (see @`taskmaster.md`)

---

## Leveling Up: Agent-Led Multi-Context Workflows

While the basic workflow is powerful, your primary opportunity to add value is by identifying when to introduce **Tagged Task Lists**. These patterns are your tools for creating a more organized and efficient development environment for the user, especially if you detect agentic or parallel development happening across the same session.

**Critical Principle**: Most users should never see a difference in their experience. Only introduce advanced workflows when you detect clear indicators that the project has evolved beyond simple task management.

### When to Introduce Tags: Your Decision Patterns

Here are the patterns to look for. When you detect one, you should propose the corresponding workflow to the user.

#### Pattern 1: Simple Git Feature Branching
This is the most common and direct use case for tags.

- **Trigger**: The user creates a new git branch (e.g., `git checkout -b feature/user-auth`).
- **Your Action**: Propose creating a new tag that mirrors the branch name to isolate the feature's tasks from `master`.
- **Your Suggested Prompt**: *"I see you've created a new branch named 'feature/user-auth'. To keep all related tasks neatly organized and separate from your main list, I can create a corresponding task tag for you. This helps prevent merge conflicts in your `tasks.json` file later. Shall I create the 'feature-user-auth' tag?"*
- **Tool to Use**: `task-master add-tag --from-branch`

#### Pattern 2: Team Collaboration
- **Trigger**: The user mentions working with teammates (e.g., "My teammate Alice is handling the database schema," or "I need to review Bob's work on the API.").
- **Your Action**: Suggest creating a separate tag for the user's work to prevent conflicts with shared master context.
- **Your Suggested Prompt**: *"Since you're working with Alice, I can create a separate task context for your work to avoid conflicts. This way, Alice can continue working with the master list while you have your own isolated context. When you're ready to merge your work, we can coordinate the tasks back to master. Shall I create a tag for your current work?"*
- **Tool to Use**: `task-master add-tag my-work --copy-from-current --description="My tasks while collaborating with Alice"`

#### Pattern 3: Experiments or Risky Refactors
- **Trigger**: The user wants to try something that might not be kept (e.g., "I want to experiment with switching our state management library," or "Let's refactor the old API module, but I want to keep the current tasks as a reference.").
- **Your Action**: Propose creating a sandboxed tag for the experimental work.
- **Your Suggested Prompt**: *"This sounds like a great experiment. To keep these new tasks separate from our main plan, I can create a temporary 'experiment-zustand' tag for this work. If we decide not to proceed, we can simply delete the tag without affecting the main task list. Sound good?"*
- **Tool to Use**: `task-master add-tag experiment-zustand --description="Exploring Zustand migration"`

#### Pattern 4: Large Feature Initiatives (PRD-Driven)
This is a more structured approach for significant new features or epics.

- **Trigger**: The user describes a large, multi-step feature that would benefit from a formal plan.
- **Your Action**: Propose a comprehensive, PRD-driven workflow.
- **Your Suggested Prompt**: *"This sounds like a significant new feature. To manage this effectively, I suggest we create a dedicated task context for it. Here's the plan: I'll create a new tag called 'feature-xyz', then we can draft a Product Requirements Document (PRD) together to scope the work. Once the PRD is ready, I'll automatically generate all the necessary tasks within that new tag. How does that sound?"*
- **Your Implementation Flow**:
    1.  **Create an empty tag**: `task-master add-tag feature-xyz --description "Tasks for the new XYZ feature"`. You can also start by creating a git branch if applicable, and then create the tag from that branch.
    2.  **Collaborate & Create PRD**: Work with the user to create a detailed PRD file (e.g., `.taskmaster/docs/feature-xyz-prd.txt`).
    3.  **Parse PRD into the new tag**: `task-master parse-prd .taskmaster/docs/feature-xyz-prd.txt --tag feature-xyz`
    4.  **Prepare the new task list**: Follow up by suggesting `analyze-complexity` and `expand-all` for the newly created tasks within the `feature-xyz` tag.

#### Pattern 5: Version-Based Development
Tailor your approach based on the project maturity indicated by tag names.

- **Prototype/MVP Tags** (`prototype`, `mvp`, `poc`, `v0.x`):
  - **Your Approach**: Focus on speed and functionality over perfection
  - **Task Generation**: Create tasks that emphasize "get it working" over "get it perfect"
  - **Complexity Level**: Lower complexity, fewer subtasks, more direct implementation paths
  - **Research Prompts**: Include context like "This is a prototype - prioritize speed and basic functionality over optimization"
  - **Example Prompt Addition**: *"Since this is for the MVP, I'll focus on tasks that get core functionality working quickly rather than over-engineering."*

- **Production/Mature Tags** (`v1.0+`, `production`, `stable`):
  - **Your Approach**: Emphasize robustness, testing, and maintainability
  - **Task Generation**: Include comprehensive error handling, testing, documentation, and optimization
  - **Complexity Level**: Higher complexity, more detailed subtasks, thorough implementation paths
  - **Research Prompts**: Include context like "This is for production - prioritize reliability, performance, and maintainability"
  - **Example Prompt Addition**: *"Since this is for production, I'll ensure tasks include proper error handling, testing, and documentation."*

### Advanced Workflow (Tag-Based & PRD-Driven)

**When to Transition**: Recognize when the project has evolved (or has initiated a project which existing code) beyond simple task management. Look for these indicators:
- User mentions teammates or collaboration needs
- Project has grown to 15+ tasks with mixed priorities
- User creates feature branches or mentions major initiatives
- User initializes Taskmaster on an existing, complex codebase
- User describes large features that would benefit from dedicated planning

**Your Role in Transition**: Guide the user to a more sophisticated workflow that leverages tags for organization and PRDs for comprehensive planning.

#### Master List Strategy (High-Value Focus)
Once you transition to tag-based workflows, the `master` tag should ideally contain only:
- **High-level deliverables** that provide significant business value
- **Major milestones** and epic-level features
- **Critical infrastructure** work that affects the entire project
- **Release-blocking** items

**What NOT to put in master**:
- Detailed implementation subtasks (these go in feature-specific tags' parent tasks)
- Refactoring work (create dedicated tags like `refactor-auth`)
- Experimental features (use `experiment-*` tags)
- Team member-specific tasks (use person-specific tags)

#### PRD-Driven Feature Development

**For New Major Features**:
1. **Identify the Initiative**: When user describes a significant feature
2. **Create Dedicated Tag**: `add_tag feature-[name] --description="[Feature description]"`
3. **Collaborative PRD Creation**: Work with user to create comprehensive PRD in `.taskmaster/docs/feature-[name]-prd.txt`
4. **Parse & Prepare**: 
   - `parse_prd .taskmaster/docs/feature-[name]-prd.txt --tag=feature-[name]`
   - `analyze_project_complexity --tag=feature-[name] --research`
   - `expand_all --tag=feature-[name] --research`
5. **Add Master Reference**: Create a high-level task in `master` that references the feature tag

**For Existing Codebase Analysis**:
When users initialize Taskmaster on existing projects:
1. **Codebase Discovery**: Use your native tools for producing deep context about the code base. You may use `research` tool with `--tree` and `--files` to collect up to date information using the existing architecture as context.
2. **Collaborative Assessment**: Work with user to identify improvement areas, technical debt, or new features
3. **Strategic PRD Creation**: Co-author PRDs that include:
   - Current state analysis (based on your codebase research)
   - Proposed improvements or new features
   - Implementation strategy considering existing code
4. **Tag-Based Organization**: Parse PRDs into appropriate tags (`refactor-api`, `feature-dashboard`, `tech-debt`, etc.)
5. **Master List Curation**: Keep only the most valuable initiatives in master

The parse-prd's `--append` flag enables the user to parse multiple PRDs within tags or across tags. PRDs should be focused and the number of tasks they are parsed into should be strategically chosen relative to the PRD's complexity and level of detail.

### Workflow Transition Examples

**Example 1: Simple → Team-Based**
```
User: "Alice is going to help with the API work"
Your Response: "Great! To avoid conflicts, I'll create a separate task context for your work. Alice can continue with the master list while you work in your own context. When you're ready to merge, we can coordinate the tasks back together."
Action: add_tag my-api-work --copy-from-current --description="My API tasks while collaborating with Alice"
```

**Example 2: Simple → PRD-Driven**
```
User: "I want to add a complete user dashboard with analytics, user management, and reporting"
Your Response: "This sounds like a major feature that would benefit from detailed planning. Let me create a dedicated context for this work and we can draft a PRD together to ensure we capture all requirements."
Actions: 
1. add_tag feature-dashboard --description="User dashboard with analytics and management"
2. Collaborate on PRD creation
3. parse_prd dashboard-prd.txt --tag=feature-dashboard
4. Add high-level "User Dashboard" task to master
```

**Example 3: Existing Project → Strategic Planning**
```
User: "I just initialized Taskmaster on my existing React app. It's getting messy and I want to improve it."
Your Response: "Let me research your codebase to understand the current architecture, then we can create a strategic plan for improvements."
Actions:
1. research "Current React app architecture and improvement opportunities" --tree --files=src/
2. Collaborate on improvement PRD based on findings
3. Create tags for different improvement areas (refactor-components, improve-state-management, etc.)
4. Keep only major improvement initiatives in master
```

---

## Primary Interaction: MCP Server vs. CLI

Taskmaster offers two primary ways to interact:

1.  **MCP Server (Recommended for Integrated Tools)**:
    - For AI agents and integrated development environments (like Cline), interacting via the **MCP server is the preferred method**.
    - The MCP server exposes Taskmaster functionality through a set of tools (e.g., `get_tasks`, `add_subtask`).
    - This method offers better performance, structured data exchange, and richer error handling compared to CLI parsing.
    - Refer to @`mcp.md` for details on the MCP architecture and available tools.
    - A comprehensive list and description of MCP tools and their corresponding CLI commands can be found in @`taskmaster.md`.
    - **Restart the MCP server** if core logic in `scripts/modules` or MCP tool/direct function definitions change.
    - **Note**: MCP tools fully support tagged task lists with complete tag management capabilities.

2.  **`task-master` CLI (For Users & Fallback)**:
    - The global `task-master` command provides a user-friendly interface for direct terminal interaction.
    - It can also serve as a fallback if the MCP server is inaccessible or a specific function isn't exposed via MCP.
    - Install globally with `npm install -g task-master-ai` or use locally via `npx task-master-ai ...`.
    - The CLI commands often mirror the MCP tools (e.g., `task-master list` corresponds to `get_tasks`).
    - Refer to @`taskmaster.md` for a detailed command reference.
    - **Tagged Task Lists**: CLI fully supports the new tagged system with seamless migration.

## How the Tag System Works (For Your Reference)

- **Data Structure**: Tasks are organized into separate contexts (tags) like "master", "feature-branch", or "v2.0".
- **Silent Migration**: Existing projects automatically migrate to use a "master" tag with zero disruption.
- **Context Isolation**: Tasks in different tags are completely separate. Changes in one tag do not affect any other tag.
- **Manual Control**: The user is always in control. There is no automatic switching. You facilitate switching by using `use-tag <name>`.
- **Full CLI & MCP Support**: All tag management commands are available through both the CLI and MCP tools for you to use. Refer to @`taskmaster.md` for a full command list.

---

## Task Complexity Analysis

-   Run `analyze_project_complexity` / `task-master analyze-complexity --research` (see @`taskmaster.md`) for comprehensive analysis
-   Review complexity report via `complexity_report` / `task-master complexity-report` (see @`taskmaster.md`) for a formatted, readable version.
-   Focus on tasks with highest complexity scores (8-10) for detailed breakdown
-   Use analysis results to determine appropriate subtask allocation
-   Note that reports are automatically used by the `expand_task` tool/command

## Task Breakdown Process

-   Use `expand_task` / `task-master expand --id=<id>`. It automatically uses the complexity report if found, otherwise generates default number of subtasks.
-   Use `--num=<number>` to specify an explicit number of subtasks, overriding defaults or complexity report recommendations.
-   Add `--research` flag to leverage Perplexity AI for research-backed expansion.
-   Add `--force` flag to clear existing subtasks before generating new ones (default is to append).
-   Use `--prompt="<context>"` to provide additional context when needed.
-   Review and adjust generated subtasks as necessary.
-   Use `expand_all` tool or `task-master expand --all` to expand multiple pending tasks at once, respecting flags like `--force` and `--research`.
-   If subtasks need complete replacement (regardless of the `--force` flag on `expand`), clear them first with `clear_subtasks` / `task-master clear-subtasks --id=<id>`.

## Implementation Drift Handling

-   When implementation differs significantly from planned approach
-   When future tasks need modification due to current implementation choices
-   When new dependencies or requirements emerge
-   Use `update` / `task-master update --from=<futureTaskId> --prompt='<explanation>\nUpdate context...' --research` to update multiple future tasks.
-   Use `update_task` / `task-master update-task --id=<taskId> --prompt='<explanation>\nUpdate context...' --research` to update a single specific task.

## Task Status Management

-   Use 'pending' for tasks ready to be worked on
-   Use 'done' for completed and verified tasks
-   Use 'deferred' for postponed tasks
-   Add custom status values as needed for project-specific workflows

## Task Structure Fields

- **id**: Unique identifier for the task (Example: `1`, `1.1`)
- **title**: Brief, descriptive title (Example: `"Initialize Repo"`)
- **description**: Concise summary of what the task involves (Example: `"Create a new repository, set up initial structure."`)
- **status**: Current state of the task (Example: `"pending"`, `"done"`, `"deferred"`)
- **dependencies**: IDs of prerequisite tasks (Example: `[1, 2.1]`)
    - Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending)
    - This helps quickly identify which prerequisite tasks are blocking work
- **priority**: Importance level (Example: `"high"`, `"medium"`, `"low"`)
- **details**: In-depth implementation instructions (Example: `"Use GitHub client ID/secret, handle callback, set session token."`) 
- **testStrategy**: Verification approach (Example: `"Deploy and call endpoint to confirm 'Hello World' response."`) 
- **subtasks**: List of smaller, more specific tasks (Example: `[{"id": 1, "title": "Configure OAuth", ...}]`) 
- Refer to task structure details (previously linked to `tasks.md`).

## Configuration Management (Updated)

Taskmaster configuration is managed through two main mechanisms:

1.  **`.taskmaster/config.json` File (Primary):**
    *   Located in the project root directory.
    *   Stores most configuration settings: AI model selections (main, research, fallback), parameters (max tokens, temperature), logging level, default subtasks/priority, project name, etc.
    *   **Tagged System Settings**: Includes `global.defaultTag` (defaults to "master") and `tags` section for tag management configuration.
    *   **Managed via `task-master models --setup` command.** Do not edit manually unless you know what you are doing.
    *   **View/Set specific models via `task-master models` command or `models` MCP tool.**
    *   Created automatically when you run `task-master models --setup` for the first time or during tagged system migration.

2.  **Environment Variables (`.env` / `mcp.json`):**
    *   Used **only** for sensitive API keys and specific endpoint URLs.
    *   Place API keys (one per provider) in a `.env` file in the project root for CLI usage.
    *   For MCP/Cline integration, configure these keys in the `env` section of `.cline/mcp.json`.
    *   Available keys/variables: See `assets/env.example` or the Configuration section in the command reference (previously linked to `taskmaster.md`).

3.  **`.taskmaster/state.json` File (Tagged System State):**
    *   Tracks current tag context and migration status.
    *   Automatically created during tagged system migration.
    *   Contains: `currentTag`, `lastSwitched`, `migrationNoticeShown`.

**Important:** Non-API key settings (like model selections, `MAX_TOKENS`, `TASKMASTER_LOG_LEVEL`) are **no longer configured via environment variables**. Use the `task-master models` command (or `--setup` for interactive configuration) or the `models` MCP tool.
**If AI commands FAIL in MCP** verify that the API key for the selected provider is present in the `env` section of `.cline/mcp.json`.
**If AI commands FAIL in CLI** verify that the API key for the selected provider is present in the `.env` file in the root of the project.

## Rules Management

Taskmaster supports multiple AI coding assistant rule sets that can be configured during project initialization or managed afterward:

- **Available Profiles**: Claude Code, Cline, Codex, Cline, Roo Code, Trae, Windsurf (claude, cline, codex, cline, roo, trae, windsurf)
- **During Initialization**: Use `task-master init --rules cline,windsurf` to specify which rule sets to include
- **After Initialization**: Use `task-master rules add <profiles>` or `task-master rules remove <profiles>` to manage rule sets
- **Interactive Setup**: Use `task-master rules setup` to launch an interactive prompt for selecting rule profiles
- **Default Behavior**: If no `--rules` flag is specified during initialization, all available rule profiles are included
- **Rule Structure**: Each profile creates its own directory (e.g., `.cline/rules`, `.roo/rules`) with appropriate configuration files

## Determining the Next Task

- Run `next_task` / `task-master next` to show the next task to work on.
- The command identifies tasks with all dependencies satisfied
- Tasks are prioritized by priority level, dependency count, and ID
- The command shows comprehensive task information including:
    - Basic task details and description
    - Implementation details
    - Subtasks (if they exist)
    - Contextual suggested actions
- Recommended before starting any new development work
- Respects your project's dependency structure
- Ensures tasks are completed in the appropriate sequence
- Provides ready-to-use commands for common task actions

## Viewing Specific Task Details

- Run `get_task` / `task-master show <id>` to view a specific task.
- Use dot notation for subtasks: `task-master show 1.2` (shows subtask 2 of task 1)
- Displays comprehensive information similar to the next command, but for a specific task
- For parent tasks, shows all subtasks and their current status
- For subtasks, shows parent task information and relationship
- Provides contextual suggested actions appropriate for the specific task
- Useful for examining task details before implementation or checking status

## Managing Task Dependencies

- Use `add_dependency` / `task-master add-dependency --id=<id> --depends-on=<id>` to add a dependency.
- Use `remove_dependency` / `task-master remove-dependency --id=<id> --depends-on=<id>` to remove a dependency.
- The system prevents circular dependencies and duplicate dependency entries
- Dependencies are checked for existence before being added or removed
- Task files are automatically regenerated after dependency changes
- Dependencies are visualized with status indicators in task listings and files

## Task Reorganization

- Use `move_task` / `task-master move --from=<id> --to=<id>` to move tasks or subtasks within the hierarchy
- This command supports several use cases:
  - Moving a standalone task to become a subtask (e.g., `--from=5 --to=7`)
  - Moving a subtask to become a standalone task (e.g., `--from=5.2 --to=7`) 
  - Moving a subtask to a different parent (e.g., `--from=5.2 --to=7.3`)
  - Reordering subtasks within the same parent (e.g., `--from=5.2 --to=5.4`)
  - Moving a task to a new, non-existent ID position (e.g., `--from=5 --to=25`)
  - Moving multiple tasks at once using comma-separated IDs (e.g., `--from=10,11,12 --to=16,17,18`)
- The system includes validation to prevent data loss:
  - Allows moving to non-existent IDs by creating placeholder tasks
  - Prevents moving to existing task IDs that have content (to avoid overwriting)
  - Validates source tasks exist before attempting to move them
- The system maintains proper parent-child relationships and dependency integrity
- Task files are automatically regenerated after the move operation
- This provides greater flexibility in organizing and refining your task structure as project understanding evolves
- This is especially useful when dealing with potential merge conflicts arising from teams creating tasks on separate branches. Solve these conflicts very easily by moving your tasks and keeping theirs.

## Iterative Subtask Implementation

Once a task has been broken down into subtasks using `expand_task` or similar methods, follow this iterative process for implementation:

1.  **Understand the Goal (Preparation):**
    *   Use `get_task` / `task-master show <subtaskId>` (see @`taskmaster.md`) to thoroughly understand the specific goals and requirements of the subtask.

2.  **Initial Exploration & Planning (Iteration 1):**
    *   This is the first attempt at creating a concrete implementation plan.
    *   Explore the codebase to identify the precise files, functions, and even specific lines of code that will need modification.
    *   Determine the intended code changes (diffs) and their locations.
    *   Gather *all* relevant details from this exploration phase.

3.  **Log the Plan:**
    *   Run `update_subtask` / `task-master update-subtask --id=<subtaskId> --prompt='<detailed plan>'`.
    *   Provide the *complete and detailed* findings from the exploration phase in the prompt. Include file paths, line numbers, proposed diffs, reasoning, and any potential challenges identified. Do not omit details. The goal is to create a rich, timestamped log within the subtask's `details`.

4.  **Verify the Plan:**
    *   Run `get_task` / `task-master show <subtaskId>` again to confirm that the detailed implementation plan has been successfully appended to the subtask's details.

5.  **Begin Implementation:**
    *   Set the subtask status using `set_task_status` / `task-master set-status --id=<subtaskId> --status=in-progress`.
    *   Start coding based on the logged plan.

6.  **Refine and Log Progress (Iteration 2+):**
    *   As implementation progresses, you will encounter challenges, discover nuances, or confirm successful approaches.
    *   **Before appending new information**: Briefly review the *existing* details logged in the subtask (using `get_task` or recalling from context) to ensure the update adds fresh insights and avoids redundancy.
    *   **Regularly** use `update_subtask` / `task-master update-subtask --id=<subtaskId> --prompt='<update details>\n- What worked...\n- What didn't work...'` to append new findings.
    *   **Crucially, log:**
        *   What worked ("fundamental truths" discovered).
        *   What didn't work and why (to avoid repeating mistakes).
        *   Specific code snippets or configurations that were successful.
        *   Decisions made, especially if confirmed with user input.
        *   Any deviations from the initial plan and the reasoning.
    *   The objective is to continuously enrich the subtask's details, creating a log of the implementation journey that helps the AI (and human developers) learn, adapt, and avoid repeating errors.

7.  **Review & Update Rules (Post-Implementation):**
    *   Once the implementation for the subtask is functionally complete, review all code changes and the relevant chat history.
    *   Identify any new or modified code patterns, conventions, or best practices established during the implementation.
    *   Create new or update existing rules following internal guidelines (previously linked to `cursor_rules.md` and `self_improve.md`).

8.  **Mark Task Complete:**
    *   After verifying the implementation and updating any necessary rules, mark the subtask as completed: `set_task_status` / `task-master set-status --id=<subtaskId> --status=done`.

9.  **Commit Changes (If using Git):**
    *   Stage the relevant code changes and any updated/new rule files (`git add .`).
    *   Craft a comprehensive Git commit message summarizing the work done for the subtask, including both code implementation and any rule adjustments.
    *   Execute the commit command directly in the terminal (e.g., `git commit -m 'feat(module): Implement feature X for subtask <subtaskId>\n\n- Details about changes...\n- Updated rule Y for pattern Z'`).
    *   Consider if a Changeset is needed according to internal versioning guidelines (previously linked to `changeset.md`). If so, run `npm run changeset`, stage the generated file, and amend the commit or create a new one.

10. **Proceed to Next Subtask:**
    *   Identify the next subtask (e.g., using `next_task` / `task-master next`).

## Code Analysis & Refactoring Techniques

- **Top-Level Function Search**:
    - Useful for understanding module structure or planning refactors.
    - Use grep/ripgrep to find exported functions/constants:
      `rg "export (async function|function|const) \w+"` or similar patterns.
    - Can help compare functions between files during migrations or identify potential naming conflicts.

---
*This workflow provides a general guideline. Adapt it based on your specific project needs and team practices.*

--- .clinerules/self_improve.md ---
---
description: Guidelines for continuously improving Cline rules based on emerging code patterns and best practices.
globs: **/*
alwaysApply: true
---

- **Rule Improvement Triggers:**
  - New code patterns not covered by existing rules
  - Repeated similar implementations across files
  - Common error patterns that could be prevented
  - New libraries or tools being used consistently
  - Emerging best practices in the codebase

- **Analysis Process:**
  - Compare new code with existing rules
  - Identify patterns that should be standardized
  - Look for references to external documentation
  - Check for consistent error handling patterns
  - Monitor test patterns and coverage

- **Rule Updates:**
  - **Add New Rules When:**
    - A new technology/pattern is used in 3+ files
    - Common bugs could be prevented by a rule
    - Code reviews repeatedly mention the same feedback
    - New security or performance patterns emerge

  - **Modify Existing Rules When:**
    - Better examples exist in the codebase
    - Additional edge cases are discovered
    - Related rules have been updated
    - Implementation details have changed

- **Example Pattern Recognition:**
  ```typescript
  // If you see repeated patterns like:
  const data = await prisma.user.findMany({
    select: { id: true, email: true },
    where: { status: 'ACTIVE' }
  });
  
  // Consider adding to [prisma.md](.clinerules/prisma.md):
  // - Standard select fields
  // - Common where conditions
  // - Performance optimization patterns
  ```

- **Rule Quality Checks:**
  - Rules should be actionable and specific
  - Examples should come from actual code
  - References should be up to date
  - Patterns should be consistently enforced

- **Continuous Improvement:**
  - Monitor code review comments
  - Track common development questions
  - Update rules after major refactors
  - Add links to relevant documentation
  - Cross-reference related rules

- **Rule Deprecation:**
  - Mark outdated patterns as deprecated
  - Remove rules that no longer apply
  - Update references to deprecated rules
  - Document migration paths for old patterns

- **Documentation Updates:**
  - Keep examples synchronized with code
  - Update references to external docs
  - Maintain links between related rules
  - Document breaking changes
Follow [cline_rules.md](.clinerules/cline_rules.md) for proper rule formatting and structure.


## Links discovered
- [prisma.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/.clinerules/.clinerules/prisma.md)
- [cline_rules.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/.clinerules/.clinerules/cline_rules.md)

--- .clinerules/taskmaster.md ---
---
description: Comprehensive reference for Taskmaster MCP tools and CLI commands.
globs: **/*
alwaysApply: true
---

# Taskmaster Tool & Command Reference

This document provides a detailed reference for interacting with Taskmaster, covering both the recommended MCP tools, suitable for integrations like Cline, and the corresponding `task-master` CLI commands, designed for direct user interaction or fallback.

**Note:** For interacting with Taskmaster programmatically or via integrated tools, using the **MCP tools is strongly recommended** due to better performance, structured data, and error handling. The CLI commands serve as a user-friendly alternative and fallback. 

**Important:** Several MCP tools involve AI processing... The AI-powered tools include `parse_prd`, `analyze_project_complexity`, `update_subtask`, `update_task`, `update`, `expand_all`, `expand_task`, and `add_task`.

**🏷️ Tagged Task Lists System:** Task Master now supports **tagged task lists** for multi-context task management. This allows you to maintain separate, isolated lists of tasks for different features, branches, or experiments. Existing projects are seamlessly migrated to use a default "master" tag. Most commands now support a `--tag <name>` flag to specify which context to operate on. If omitted, commands use the currently active tag.

---

## Initialization & Setup

### 1. Initialize Project (`init`)

*   **MCP Tool:** `initialize_project`
*   **CLI Command:** `task-master init [options]`
*   **Description:** `Set up the basic Taskmaster file structure and configuration in the current directory for a new project.`
*   **Key CLI Options:**
    *   `--name <name>`: `Set the name for your project in Taskmaster's configuration.`
    *   `--description <text>`: `Provide a brief description for your project.`
    *   `--version <version>`: `Set the initial version for your project, e.g., '0.1.0'.`
    *   `-y, --yes`: `Initialize Taskmaster quickly using default settings without interactive prompts.`
*   **Usage:** Run this once at the beginning of a new project.
*   **MCP Variant Description:** `Set up the basic Taskmaster file structure and configuration in the current directory for a new project by running the 'task-master init' command.`
*   **Key MCP Parameters/Options:**
    *   `projectName`: `Set the name for your project.` (CLI: `--name <name>`)
    *   `projectDescription`: `Provide a brief description for your project.` (CLI: `--description <text>`)
    *   `projectVersion`: `Set the initial version for your project, e.g., '0.1.0'.` (CLI: `--version <version>`)
    *   `authorName`: `Author name.` (CLI: `--author <author>`)
    *   `skipInstall`: `Skip installing dependencies. Default is false.` (CLI: `--skip-install`)
    *   `addAliases`: `Add shell aliases tm, taskmaster, hamster, and ham. Default is false.` (CLI: `--aliases`)
    *   `yes`: `Skip prompts and use defaults/provided arguments. Default is false.` (CLI: `-y, --yes`)
*   **Usage:** Run this once at the beginning of a new project, typically via an integrated tool like Cline. Operates on the current working directory of the MCP server. 
*   **Important:** Once complete, you *MUST* parse a prd in order to generate tasks. There will be no tasks files until then. The next step after initializing should be to create a PRD using the example PRD in .taskmaster/templates/example_prd.txt. 
*   **Tagging:** Use the `--tag` option to parse the PRD into a specific, non-default tag context. If the tag doesn't exist, it will be created automatically. Example: `task-master parse-prd spec.txt --tag=new-feature`.

### 2. Parse PRD (`parse_prd`)

*   **MCP Tool:** `parse_prd`
*   **CLI Command:** `task-master parse-prd [file] [options]`
*   **Description:** `Parse a Product Requirements Document, PRD, or text file with Taskmaster to automatically generate an initial set of tasks in tasks.json.`
*   **Key Parameters/Options:**
    *   `input`: `Path to your PRD or requirements text file that Taskmaster should parse for tasks.` (CLI: `[file]` positional or `-i, --input <file>`)
    *   `output`: `Specify where Taskmaster should save the generated 'tasks.json' file. Defaults to '.taskmaster/tasks/tasks.json'.` (CLI: `-o, --output <file>`)
    *   `numTasks`: `Approximate number of top-level tasks Taskmaster should aim to generate from the document.` (CLI: `-n, --num-tasks <number>`)
    *   `force`: `Use this to allow Taskmaster to overwrite an existing 'tasks.json' without asking for confirmation.` (CLI: `-f, --force`)
*   **Usage:** Useful for bootstrapping a project from an existing requirements document.
*   **Notes:** Task Master will strictly adhere to any specific requirements mentioned in the PRD, such as libraries, database schemas, frameworks, tech stacks, etc., while filling in any gaps where the PRD isn't fully specified. Tasks are designed to provide the most direct implementation path while avoiding over-engineering.
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. If the user does not have a PRD, suggest discussing their idea and then use the example PRD in `.taskmaster/templates/example_prd.txt` as a template for creating the PRD based on their idea, for use with `parse-prd`.

---

## AI Model Configuration

### 2. Manage Models (`models`)
*   **MCP Tool:** `models`
*   **CLI Command:** `task-master models [options]`
*   **Description:** `View the current AI model configuration or set specific models for different roles (main, research, fallback). Allows setting custom model IDs for Ollama and OpenRouter.`
*   **Key MCP Parameters/Options:**
    *   `setMain <model_id>`: `Set the primary model ID for task generation/updates.` (CLI: `--set-main <model_id>`)
    *   `setResearch <model_id>`: `Set the model ID for research-backed operations.` (CLI: `--set-research <model_id>`)
    *   `setFallback <model_id>`: `Set the model ID to use if the primary fails.` (CLI: `--set-fallback <model_id>`)
    *   `ollama <boolean>`: `Indicates the set model ID is a custom Ollama model.` (CLI: `--ollama`)
    *   `openrouter <boolean>`: `Indicates the set model ID is a custom OpenRouter model.` (CLI: `--openrouter`)
    *   `listAvailableModels <boolean>`: `If true, lists available models not currently assigned to a role.` (CLI: No direct equivalent; CLI lists available automatically)
    *   `projectRoot <string>`: `Optional. Absolute path to the project root directory.` (CLI: Determined automatically)
*   **Key CLI Options:**
    *   `--set-main <model_id>`: `Set the primary model.`
    *   `--set-research <model_id>`: `Set the research model.`
    *   `--set-fallback <model_id>`: `Set the fallback model.`
    *   `--ollama`: `Specify that the provided model ID is for Ollama (use with --set-*).`
    *   `--openrouter`: `Specify that the provided model ID is for OpenRouter (use with --set-*). Validates against OpenRouter API.`
    *   `--bedrock`: `Specify that the provided model ID is for AWS Bedrock (use with --set-*).`
    *   `--setup`: `Run interactive setup to configure models, including custom Ollama/OpenRouter IDs.`
*   **Usage (MCP):** Call without set flags to get current config. Use `setMain`, `setResearch`, or `setFallback` with a valid model ID to update the configuration. Use `listAvailableModels: true` to get a list of unassigned models. To set a custom model, provide the model ID and set `ollama: true` or `openrouter: true`.
*   **Usage (CLI):** Run without flags to view current configuration and available models. Use set flags to update specific roles. Use `--setup` for guided configuration, including custom models. To set a custom model via flags, use `--set-<role>=<model_id>` along with either `--ollama` or `--openrouter`.
*   **Notes:** Configuration is stored in `.taskmaster/config.json` in the project root. This command/tool modifies that file. Use `listAvailableModels` or `task-master models` to see internally supported models. OpenRouter custom models are validated against their live API. Ollama custom models are not validated live.
*   **API note:** API keys for selected AI providers (based on their model) need to exist in the mcp.json file to be accessible in MCP context. The API keys must be present in the local .env file for the CLI to be able to read them.
*   **Model costs:** The costs in supported models are expressed in dollars. An input/output value of 3 is $3.00. A value of 0.8 is $0.80. 
*   **Warning:** DO NOT MANUALLY EDIT THE .taskmaster/config.json FILE. Use the included commands either in the MCP or CLI format as needed. Always prioritize MCP tools when available and use the CLI as a fallback.

---

## Task Listing & Viewing

### 3. Get Tasks (`get_tasks`)

*   **MCP Tool:** `get_tasks`
*   **CLI Command:** `task-master list [options]`
*   **Description:** `List your Taskmaster tasks, optionally filtering by status and showing subtasks.`
*   **Key Parameters/Options:**
    *   `status`: `Show only Taskmaster tasks matching this status (or multiple statuses, comma-separated), e.g., 'pending' or 'done,in-progress'.` (CLI: `-s, --status <status>`)
    *   `withSubtasks`: `Include subtasks indented under their parent tasks in the list.` (CLI: `--with-subtasks`)
    *   `tag`: `Specify which tag context to list tasks from. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
    *   `watch`: `Watch for changes and auto-refresh the list in real-time. Works with file storage (fs.watch) and API storage (Supabase Realtime).` (CLI: `-w, --watch`)
*   **Usage:** Get an overview of the project status, often used at the start of a work session. Use `--watch` to keep the list live-updating as tasks change.

### 4. Get Next Task (`next_task`)

*   **MCP Tool:** `next_task`
*   **CLI Command:** `task-master next [options]`
*   **Description:** `Ask Taskmaster to show the next available task you can work on, based on status and completed dependencies.`
*   **Key Parameters/Options:**
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
    *   `tag`: `Specify which tag context to use. Defaults to the current active tag.` (CLI: `--tag <name>`)
*   **Usage:** Identify what to work on next according to the plan.

### 5. Get Task Details (`get_task`)

*   **MCP Tool:** `get_task`
*   **CLI Command:** `task-master show [id] [options]`
*   **Description:** `Display detailed information for one or more specific Taskmaster tasks or subtasks by ID.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The ID of the Taskmaster task (e.g., '15'), subtask (e.g., '15.2'), or a comma-separated list of IDs ('1,5,10.2') you want to view.` (CLI: `[id]` positional or `-i, --id <id>`)
    *   `tag`: `Specify which tag context to get the task(s) from. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Understand the full details for a specific task. When multiple IDs are provided, a summary table is shown.
*   **CRITICAL INFORMATION** If you need to collect information from multiple tasks, use comma-separated IDs (i.e. 1,2,3) to receive an array of tasks. Do not needlessly get tasks one at a time if you need to get many as that is wasteful.

---

## Task Creation & Modification

### 6. Add Task (`add_task`)

*   **MCP Tool:** `add_task`
*   **CLI Command:** `task-master add-task [options]`
*   **Description:** `Add a new task to Taskmaster by describing it; AI will structure it.`
*   **Key Parameters/Options:**
    *   `prompt`: `Required. Describe the new task you want Taskmaster to create, e.g., "Implement user authentication using JWT".` (CLI: `-p, --prompt <text>`)
    *   `dependencies`: `Specify the IDs of any Taskmaster tasks that must be completed before this new one can start, e.g., '12,14'.` (CLI: `-d, --dependencies <ids>`)
    *   `priority`: `Set the priority for the new task: 'high', 'medium', or 'low'. Default is 'medium'.` (CLI: `--priority <priority>`)
    *   `research`: `Enable Taskmaster to use the research role for potentially more informed task creation.` (CLI: `-r, --research`)
    *   `tag`: `Specify which tag context to add the task to. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Quickly add newly identified tasks during development.
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.

### 7. Add Subtask (`add_subtask`)

*   **MCP Tool:** `add_subtask`
*   **CLI Command:** `task-master add-subtask [options]`
*   **Description:** `Add a new subtask to a Taskmaster parent task, or convert an existing task into a subtask.`
*   **Key Parameters/Options:**
    *   `id` / `parent`: `Required. The ID of the Taskmaster task that will be the parent.` (MCP: `id`, CLI: `-p, --parent <id>`)
    *   `taskId`: `Use this if you want to convert an existing top-level Taskmaster task into a subtask of the specified parent.` (CLI: `-i, --task-id <id>`)
    *   `title`: `Required if not using taskId. The title for the new subtask Taskmaster should create.` (CLI: `-t, --title <title>`)
    *   `description`: `A brief description for the new subtask.` (CLI: `-d, --description <text>`)
    *   `details`: `Provide implementation notes or details for the new subtask.` (CLI: `--details <text>`)
    *   `dependencies`: `Specify IDs of other tasks or subtasks, e.g., '15' or '16.1', that must be done before this new subtask.` (CLI: `--dependencies <ids>`)
    *   `status`: `Set the initial status for the new subtask. Default is 'pending'.` (CLI: `-s, --status <status>`)
    *   `generate`: `Enable Taskmaster to regenerate markdown task files after adding the subtask.` (CLI: `--generate`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Break down tasks manually or reorganize existing tasks.

### 8. Update Tasks (`update`)

*   **MCP Tool:** `update`
*   **CLI Command:** `task-master update [options]`
*   **Description:** `Update multiple upcoming tasks in Taskmaster based on new context or changes, starting from a specific task ID.`
*   **Key Parameters/Options:**
    *   `from`: `Required. The ID of the first task Taskmaster should update. All tasks with this ID or higher that are not 'done' will be considered.` (CLI: `--from <id>`)
    *   `prompt`: `Required. Explain the change or new context for Taskmaster to apply to the tasks, e.g., "We are now using React Query instead of Redux Toolkit for data fetching".` (CLI: `-p, --prompt <text>`)
    *   `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Handle significant implementation changes or pivots that affect multiple future tasks. Example CLI: `task-master update --from='18' --prompt='Switching to React Query.\nNeed to refactor data fetching...'`
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.

### 9. Update Task (`update_task`)

*   **MCP Tool:** `update_task`
*   **CLI Command:** `task-master update-task [options]`
*   **Description:** `Modify a specific Taskmaster task by ID, incorporating new information or changes. By default, this replaces the existing task details.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The specific ID of the Taskmaster task, e.g., '15', you want to update.` (CLI: `-i, --id <id>`)
    *   `prompt`: `Required. Explain the specific changes or provide the new information Taskmaster should incorporate into this task.` (CLI: `-p, --prompt <text>`)
    *   `append`: `If true, appends the prompt content to the task's details with a timestamp, rather than replacing them. Behaves like update-subtask.` (CLI: `--append`)
    *   `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`)
    *   `tag`: `Specify which tag context the task belongs to. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Refine a specific task based on new understanding. Use `--append` to log progress without creating subtasks.
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.

### 10. Update Subtask (`update_subtask`)

*   **MCP Tool:** `update_subtask`
*   **CLI Command:** `task-master update-subtask [options]`
*   **Description:** `Append timestamped notes or details to a specific Taskmaster subtask without overwriting existing content. Intended for iterative implementation logging.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The ID of the Taskmaster subtask, e.g., '5.2', to update with new information.` (CLI: `-i, --id <id>`)
    *   `prompt`: `Required. The information, findings, or progress notes to append to the subtask's details with a timestamp.` (CLI: `-p, --prompt <text>`)
    *   `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`)
    *   `tag`: `Specify which tag context the subtask belongs to. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Log implementation progress, findings, and discoveries during subtask development. Each update is timestamped and appended to preserve the implementation journey.
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.

### 11. Set Task Status (`set_task_status`)

*   **MCP Tool:** `set_task_status`
*   **CLI Command:** `task-master set-status [options]`
*   **Description:** `Update the status of one or more Taskmaster tasks or subtasks, e.g., 'pending', 'in-progress', 'done'.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The ID(s) of the Taskmaster task(s) or subtask(s), e.g., '15', '15.2', or '16,17.1', to update.` (CLI: `-i, --id <id>`)
    *   `status`: `Required. The new status to set, e.g., 'done', 'pending', 'in-progress', 'review', 'cancelled'.` (CLI: `-s, --status <status>`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Mark progress as tasks move through the development cycle.

### 12. Remove Task (`remove_task`)

*   **MCP Tool:** `remove_task`
*   **CLI Command:** `task-master remove-task [options]`
*   **Description:** `Permanently remove a task or subtask from the Taskmaster tasks list.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The ID of the Taskmaster task, e.g., '5', or subtask, e.g., '5.2', to permanently remove.` (CLI: `-i, --id <id>`)
    *   `yes`: `Skip the confirmation prompt and immediately delete the task.` (CLI: `-y, --yes`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Permanently delete tasks or subtasks that are no longer needed in the project.
*   **Notes:** Use with caution as this operation cannot be undone. Consider using 'blocked', 'cancelled', or 'deferred' status instead if you just want to exclude a task from active planning but keep it for reference. The command automatically cleans up dependency references in other tasks.

---

## Task Structure & Breakdown

### 13. Expand Task (`expand_task`)

*   **MCP Tool:** `expand_task`
*   **CLI Command:** `task-master expand [options]`
*   **Description:** `Use Taskmaster's AI to break down a complex task into smaller, manageable subtasks. Appends subtasks by default.`
*   **Key Parameters/Options:**
    *   `id`: `The ID of the specific Taskmaster task you want to break down into subtasks.` (CLI: `-i, --id <id>`)
    *   `num`: `Optional: Suggests how many subtasks Taskmaster should aim to create. Uses complexity analysis/defaults otherwise.` (CLI: `-n, --num <number>`)
    *   `research`: `Enable Taskmaster to use the research role for more informed subtask generation. Requires appropriate API key.` (CLI: `-r, --research`)
    *   `prompt`: `Optional: Provide extra context or specific instructions to Taskmaster for generating the subtasks.` (CLI: `-p, --prompt <text>`)
    *   `force`: `Optional: If true, clear existing subtasks before generating new ones. Default is false (append).` (CLI: `--force`)
    *   `tag`: `Specify which tag context the task belongs to. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Generate a detailed implementation plan for a complex task before starting coding. Automatically uses complexity report recommendations if available and `num` is not specified.
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.

### 14. Expand All Tasks (`expand_all`)

*   **MCP Tool:** `expand_all`
*   **CLI Command:** `task-master expand --all [options]` (Note: CLI uses the `expand` command with the `--all` flag)
*   **Description:** `Tell Taskmaster to automatically expand all eligible pending/in-progress tasks based on complexity analysis or defaults. Appends subtasks by default.`
*   **Key Parameters/Options:**
    *   `num`: `Optional: Suggests how many subtasks Taskmaster should aim to create per task.` (CLI: `-n, --num <number>`)
    *   `research`: `Enable research role for more informed subtask generation. Requires appropriate API key.` (CLI: `-r, --research`)
    *   `prompt`: `Optional: Provide extra context for Taskmaster to apply generally during expansion.` (CLI: `-p, --prompt <text>`)
    *   `force`: `Optional: If true, clear existing subtasks before generating new ones for each eligible task. Default is false (append).` (CLI: `--force`)
    *   `tag`: `Specify which tag context to expand. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Useful after initial task generation or complexity analysis to break down multiple tasks at once.
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.

### 15. Clear Subtasks (`clear_subtasks`)

*   **MCP Tool:** `clear_subtasks`
*   **CLI Command:** `task-master clear-subtasks [options]`
*   **Description:** `Remove all subtasks from one or more specified Taskmaster parent tasks.`
*   **Key Parameters/Options:**
    *   `id`: `The ID(s) of the Taskmaster parent task(s) whose subtasks you want to remove, e.g., '15' or '16,18'. Required unless using 'all'.` (CLI: `-i, --id <ids>`)
    *   `all`: `Tell Taskmaster to remove subtasks from all parent tasks.` (CLI: `--all`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Used before regenerating subtasks with `expand_task` if the previous breakdown needs replacement.

### 16. Remove Subtask (`remove_subtask`)

*   **MCP Tool:** `remove_subtask`
*   **CLI Command:** `task-master remove-subtask [options]`
*   **Description:** `Remove a subtask from its Taskmaster parent, optionally converting it into a standalone task.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The ID(s) of the Taskmaster subtask(s) to remove, e.g., '15.2' or '16.1,16.3'.` (CLI: `-i, --id <id>`)
    *   `convert`: `If used, Taskmaster will turn the subtask into a regular top-level task instead of deleting it.` (CLI: `-c, --convert`)
    *   `generate`: `Enable Taskmaster to regenerate markdown task files after removing the subtask.` (CLI: `--generate`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Delete unnecessary subtasks or promote a subtask to a top-level task.

### 17. Move Task (`move_task`)

*   **MCP Tool:** `move_task`
*   **CLI Command:** `task-master move [options]`
*   **Description:** `Move a task or subtask to a new position within the task hierarchy.`
*   **Key Parameters/Options:**
    *   `from`: `Required. ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated for multiple tasks.` (CLI: `--from <id>`)
    *   `to`: `Required. ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated.` (CLI: `--to <id>`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Reorganize tasks by moving them within the hierarchy. Supports various scenarios like:
    *   Moving a task to become a subtask
    *   Moving a subtask to become a standalone task
    *   Moving a subtask to a different parent
    *   Reordering subtasks within the same parent
    *   Moving a task to a new, non-existent ID (automatically creates placeholders)
    *   Moving multiple tasks at once with comma-separated IDs
*   **Validation Features:**
    *   Allows moving tasks to non-existent destination IDs (creates placeholder tasks)
    *   Prevents moving to existing task IDs that already have content (to avoid overwriting)
    *   Validates that source tasks exist before attempting to move them
    *   Maintains proper parent-child relationships
*   **Example CLI:** `task-master move --from=5.2 --to=7.3` to move subtask 5.2 to become subtask 7.3.
*   **Example Multi-Move:** `task-master move --from=10,11,12 --to=16,17,18` to move multiple tasks to new positions.
*   **Common Use:** Resolving merge conflicts in tasks.json when multiple team members create tasks on different branches.

---

## Dependency Management

### 18. Add Dependency (`add_dependency`)

*   **MCP Tool:** `add_dependency`
*   **CLI Command:** `task-master add-dependency [options]`
*   **Description:** `Define a dependency in Taskmaster, making one task a prerequisite for another.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The ID of the Taskmaster task that will depend on another.` (CLI: `-i, --id <id>`)
    *   `dependsOn`: `Required. The ID of the Taskmaster task that must be completed first, the prerequisite.` (CLI: `-d, --depends-on <id>`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <path>`)
*   **Usage:** Establish the correct order of execution between tasks.

### 19. Remove Dependency (`remove_dependency`)

*   **MCP Tool:** `remove_dependency`
*   **CLI Command:** `task-master remove-dependency [options]`
*   **Description:** `Remove a dependency relationship between two Taskmaster tasks.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The ID of the Taskmaster task you want to remove a prerequisite from.` (CLI: `-i, --id <id>`)
    *   `dependsOn`: `Required. The ID of the Taskmaster task that should no longer be a prerequisite.` (CLI: `-d, --depends-on <id>`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Update task relationships when the order of execution changes.

### 20. Validate Dependencies (`validate_dependencies`)

*   **MCP Tool:** `validate_dependencies`
*   **CLI Command:** `task-master validate-dependencies [options]`
*   **Description:** `Check your Taskmaster tasks for dependency issues (like circular references or links to non-existent tasks) without making changes.`
*   **Key Parameters/Options:**
    *   `tag`: `Specify which tag context to validate. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Audit the integrity of your task dependencies.

### 21. Fix Dependencies (`fix_dependencies`)

*   **MCP Tool:** `fix_dependencies`
*   **CLI Command:** `task-master fix-dependencies [options]`
*   **Description:** `Automatically fix dependency issues (like circular references or links to non-existent tasks) in your Taskmaster tasks.`
*   **Key Parameters/Options:**
    *   `tag`: `Specify which tag context to fix dependencies in. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Clean up dependency errors automatically.

---

## Analysis & Reporting

### 22. Analyze Project Complexity (`analyze_project_complexity`)

*   **MCP Tool:** `analyze_project_complexity`
*   **CLI Command:** `task-master analyze-complexity [options]`
*   **Description:** `Have Taskmaster analyze your tasks to determine their complexity and suggest which ones need to be broken down further.`
*   **Key Parameters/Options:**
    *   `output`: `Where to save the complexity analysis report. Default is '.taskmaster/reports/task-complexity-report.json' (or '..._tagname.json' if a tag is used).` (CLI: `-o, --output <file>`)
    *   `threshold`: `The minimum complexity score (1-10) that should trigger a recommendation to expand a task.` (CLI: `-t, --threshold <number>`)
    *   `research`: `Enable research role for more accurate complexity analysis. Requires appropriate API key.` (CLI: `-r, --research`)
    *   `tag`: `Specify which tag context to analyze. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Used before breaking down tasks to identify which ones need the most attention.
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.

### 23. View Complexity Report (`complexity_report`)

*   **MCP Tool:** `complexity_report`
*   **CLI Command:** `task-master complexity-report [options]`
*   **Description:** `Display the task complexity analysis report in a readable format.`
*   **Key Parameters/Options:**
    *   `tag`: `Specify which tag context to show the report for. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to the complexity report (default: '.taskmaster/reports/task-complexity-report.json').` (CLI: `-f, --file <file>`)
*   **Usage:** Review and understand the complexity analysis results after running analyze-complexity.

---

## File Management

### 24. Generate Task Files (`generate`)

*   **MCP Tool:** `generate`
*   **CLI Command:** `task-master generate [options]`
*   **Description:** `Create or update individual Markdown files for each task based on your tasks.json.`
*   **Key Parameters/Options:**
    *   `output`: `The directory where Taskmaster should save the task files (default: in a 'tasks' directory).` (CLI: `-o, --output <directory>`)
    *   `tag`: `Specify which tag context to generate files for. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Run this after making changes to tasks.json to keep individual task files up to date. This command is now manual and no longer runs automatically.

---

## AI-Powered Research

### 25. Research (`research`)

*   **MCP Tool:** `research`
*   **CLI Command:** `task-master research [options]`
*   **Description:** `Perform AI-powered research queries with project context to get fresh, up-to-date information beyond the AI's knowledge cutoff.`
*   **Key Parameters/Options:**
    *   `query`: `Required. Research query/prompt (e.g., "What are the latest best practices for React Query v5?").` (CLI: `[query]` positional or `-q, --query <text>`)
    *   `taskIds`: `Comma-separated list of task/subtask IDs from the current tag context (e.g., "15,16.2,17").` (CLI: `-i, --id <ids>`)
    *   `filePaths`: `Comma-separated list of file paths for context (e.g., "src/api.js,docs/readme.md").` (CLI: `-f, --files <paths>`)
    *   `customContext`: `Additional custom context text to include in the research.` (CLI: `-c, --context <text>`)
    *   `includeProjectTree`: `Include project file tree structure in context (default: false).` (CLI: `--tree`)
    *   `detailLevel`: `Detail level for the research response: 'low', 'medium', 'high' (default: medium).` (CLI: `--detail <level>`)
    *   `saveTo`: `Task or subtask ID (e.g., "15", "15.2") to automatically save the research conversation to.` (CLI: `--save-to <id>`)
    *   `saveFile`: `If true, saves the research conversation to a markdown file in '.taskmaster/docs/research/'.` (CLI: `--save-file`)
    *   `noFollowup`: `Disables the interactive follow-up question menu in the CLI.` (CLI: `--no-followup`)
    *   `tag`: `Specify which tag context to use for task-based context gathering. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `projectRoot`: `The directory of the project. Must be an absolute path.` (CLI: Determined automatically)
*   **Usage:** **This is a POWERFUL tool that agents should use FREQUENTLY** to:
    *   Get fresh information beyond knowledge cutoff dates
    *   Research latest best practices, library updates, security patches
    *   Find implementation examples for specific technologies
    *   Validate approaches against current industry standards
    *   Get contextual advice based on project files and tasks
*   **When to Consider Using Research:**
    *   **Before implementing any task** - Research current best practices
    *   **When encountering new technologies** - Get up-to-date implementation guidance (libraries, apis, etc)
    *   **For security-related tasks** - Find latest security recommendations
    *   **When updating dependencies** - Research breaking changes and migration guides
    *   **For performance optimization** - Get current performance best practices
    *   **When debugging complex issues** - Research known solutions and workarounds
*   **Research + Action Pattern:**
    *   Use `research` to gather fresh information
    *   Use `update_subtask` to commit findings with timestamps
    *   Use `update_task` to incorporate research into task details
    *   Use `add_task` with research flag for informed task creation
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. The research provides FRESH data beyond the AI's training cutoff, making it invaluable for current best practices and recent developments.

---

## Tag Management

This new suite of commands allows you to manage different task contexts (tags).

### 26. List Tags (`tags`)

*   **MCP Tool:** `list_tags`
*   **CLI Command:** `task-master tags [options]`
*   **Description:** `List all available tags with task counts, completion status, and other metadata.`
*   **Key Parameters/Options:**
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
    *   `--show-metadata`: `Include detailed metadata in the output (e.g., creation date, description).` (CLI: `--show-metadata`)

### 27. Add Tag (`add_tag`)

*   **MCP Tool:** `add_tag`
*   **CLI Command:** `task-master add-tag <tagName> [options]`
*   **Description:** `Create a new, empty tag context, or copy tasks from another tag.`
*   **Key Parameters/Options:**
    *   `tagName`: `Name of the new tag to create (alphanumeric, hyphens, underscores).` (CLI: `<tagName>` positional)
    *   `--from-branch`: `Creates a tag with a name derived from the current git branch, ignoring the <tagName> argument.` (CLI: `--from-branch`)
    *   `--copy-from-current`: `Copy tasks from the currently active tag to the new tag.` (CLI: `--copy-from-current`)
    *   `--copy-from <tag>`: `Copy tasks from a specific source tag to the new tag.` (CLI: `--copy-from <tag>`)
    *   `--description <text>`: `Provide an optional description for the new tag.` (CLI: `-d, --description <text>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)

### 28. Delete Tag (`delete_tag`)

*   **MCP Tool:** `delete_tag`
*   **CLI Command:** `task-master delete-tag <tagName> [options]`
*   **Description:** `Permanently delete a tag and all of its associated tasks.`
*   **Key Parameters/Options:**
    *   `tagName`: `Name of the tag to delete.` (CLI: `<tagName>` positional)
    *   `--yes`: `Skip the confirmation prompt.` (CLI: `-y, --yes`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)

### 29. Use Tag (`use_tag`)

*   **MCP Tool:** `use_tag`
*   **CLI Command:** `task-master use-tag <tagName>`
*   **Description:** `Switch your active task context to a different tag.`
*   **Key Parameters/Options:**
    *   `tagName`: `Name of the tag to switch to.` (CLI: `<tagName>` positional)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)

### 30. Rename Tag (`rename_tag`)

*   **MCP Tool:** `rename_tag`
*   **CLI Command:** `task-master rename-tag <oldName> <newName>`
*   **Description:** `Rename an existing tag.`
*   **Key Parameters/Options:**
    *   `oldName`: `The current name of the tag.` (CLI: `<oldName>` positional)
    *   `newName`: `The new name for the tag.` (CLI: `<newName>` positional)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)

### 31. Copy Tag (`copy_tag`)

*   **MCP Tool:** `copy_tag`
*   **CLI Command:** `task-master copy-tag <sourceName> <targetName> [options]`
*   **Description:** `Copy an entire tag context, including all its tasks and metadata, to a new tag.`
*   **Key Parameters/Options:**
    *   `sourceName`: `Name of the tag to copy from.` (CLI: `<sourceName>` positional)
    *   `targetName`: `Name of the new tag to create.` (CLI: `<targetName>` positional)
    *   `--description <text>`: `Optional description for the new tag.` (CLI: `-d, --description <text>`)

---

## Miscellaneous

### 32. Sync Readme (`sync-readme`) -- experimental

*   **MCP Tool:** N/A
*   **CLI Command:** `task-master sync-readme [options]`
*   **Description:** `Exports your task list to your project's README.md file, useful for showcasing progress.`
*   **Key Parameters/Options:**
    *   `status`: `Filter tasks by status (e.g., 'pending', 'done').` (CLI: `-s, --status <status>`)
    *   `withSubtasks`: `Include subtasks in the export.` (CLI: `--with-subtasks`)
    *   `tag`: `Specify which tag context to export from. Defaults to the current active tag.` (CLI: `--tag <name>`)

---

## Environment Variables Configuration (Updated)

Taskmaster primarily uses the **`.taskmaster/config.json`** file (in project root) for configuration (models, parameters, logging level, etc.), managed via `task-master models --setup`.

Environment variables are used **only** for sensitive API keys related to AI providers and specific overrides like the Ollama base URL:

*   **API Keys (Required for corresponding provider):**
    *   `ANTHROPIC_API_KEY`
    *   `PERPLEXITY_API_KEY`
    *   `OPENAI_API_KEY`
    *   `GOOGLE_API_KEY`
    *   `MISTRAL_API_KEY`
    *   `AZURE_OPENAI_API_KEY` (Requires `AZURE_OPENAI_ENDPOINT` too)
    *   `OPENROUTER_API_KEY`
    *   `XAI_API_KEY`
    *   `OLLAMA_API_KEY` (Requires `OLLAMA_BASE_URL` too)
*   **Endpoints (Optional/Provider Specific inside .taskmaster/config.json):**
    *   `AZURE_OPENAI_ENDPOINT`
    *   `OLLAMA_BASE_URL` (Default: `http://localhost:11434/api`)

**Set API keys** in your **`.env`** file in the project root (for CLI use) or within the `env` section of your **`.cline/mcp.json`** file (for MCP/Cline integration). All other settings (model choice, max tokens, temperature, log level, custom endpoints) are managed in `.taskmaster/config.json` via `task-master models` command or `models` MCP tool.

---

## MCP Tool Tiers

Default: `core` (7 tools). Set via `TASK_MASTER_TOOLS` env var in MCP config.

| Tier | Count | Tools |
|------|-------|-------|
| `core` | 7 | `get_tasks`, `next_task`, `get_task`, `set_task_status`, `update_subtask`, `parse_prd`, `expand_task` |
| `standard` | 14 | core + `initialize_project`, `analyze_project_complexity`, `expand_all`, `add_subtask`, `remove_task`, `add_task`, `complexity_report` |
| `all` | 44+ | standard + dependencies, tags, research, autopilot, scoping, models, rules |

**Upgrade when tool unavailable:** Edit MCP config (`.cline/mcp.json`, `.mcp.json`, or `.vscode/mcp.json`), change `TASK_MASTER_TOOLS` from `"core"` to `"standard"` or `"all"`, restart MCP.

---

For details on how these commands fit into the development process, see the [dev_workflow.md](.clinerules/dev_workflow.md).

## Links discovered
- [dev_workflow.md](https://github.com/AcidicSoil/llms-txt-registry/blob/main/.clinerules/.clinerules/dev_workflow.md)

--- .cursor/commands/tm/add-dependency.md ---
Add a dependency between tasks.

Arguments: $ARGUMENTS

Parse the task IDs to establish dependency relationship.

## Adding Dependencies

Creates a dependency where one task must be completed before another can start.

## Argument Parsing

Parse natural language or IDs:
- "make 5 depend on 3" → task 5 depends on task 3
- "5 needs 3" → task 5 depends on task 3
- "5 3" → task 5 depends on task 3
- "5 after 3" → task 5 depends on task 3

## Execution

```bash
task-master add-dependency --id=<task-id> --depends-on=<dependency-id>
```

## Validation

Before adding:
1. **Verify both tasks exist**
2. **Check for circular dependencies**
3. **Ensure dependency makes logical sense**
4. **Warn if creating complex chains**

## Smart Features

- Detect if dependency already exists
- Suggest related dependencies
- Show impact on task flow
- Update task priorities if needed

## Post-Addition

After adding dependency:
1. Show updated dependency graph
2. Identify any newly blocked tasks
3. Suggest task order changes
4. Update project timeline

## Example Flows

```
/taskmaster:add-dependency 5 needs 3
→ Task #5 now depends on Task #3
→ Task #5 is now blocked until #3 completes
→ Suggested: Also consider if #5 needs #4
```

--- .cursor/commands/tm/add-subtask.md ---
Add a subtask to a parent task.

Arguments: $ARGUMENTS

Parse arguments to create a new subtask or convert existing task.

## Adding Subtasks

Creates subtasks to break down complex parent tasks into manageable pieces.

## Argument Parsing

Flexible natural language:
- "add subtask to 5: implement login form"
- "break down 5 with: setup, implement, test"
- "subtask for 5: handle edge cases"
- "5: validate user input" → adds subtask to task 5

## Execution Modes

### 1. Create New Subtask
```bash
task-master add-subtask --parent=<id> --title="<title>" --description="<desc>"
```

### 2. Convert Existing Task
```bash
task-master add-subtask --parent=<id> --task-id=<existing-id>
```

## Smart Features

1. **Automatic Subtask Generation**
   - If title contains "and" or commas, create multiple
   - Suggest common subtask patterns
   - Inherit parent's context

2. **Intelligent Defaults**
   - Priority based on parent
   - Appropriate time estimates
   - Logical dependencies between subtasks

3. **Validation**
   - Check parent task complexity
   - Warn if too many subtasks
   - Ensure subtask makes sense

## Creation Process

1. Parse parent task context
2. Generate subtask with ID like "5.1"
3. Set appropriate defaults
4. Link to parent task
5. Update parent's time estimate

## Example Flows

```
/taskmaster:add-subtask to 5: implement user authentication
→ Created subtask #5.1: "implement user authentication"
→ Parent task #5 now has 1 subtask
→ Suggested next subtasks: tests, documentation

/taskmaster:add-subtask 5: setup, implement, test
→ Created 3 subtasks:
  #5.1: setup
  #5.2: implement
  #5.3: test
```

## Post-Creation

- Show updated task hierarchy
- Suggest logical next subtasks
- Update complexity estimates
- Recommend subtask order

--- .cursor/commands/tm/add-task.md ---
Add new tasks with intelligent parsing and context awareness.

Arguments: $ARGUMENTS

## Smart Task Addition

Parse natural language to create well-structured tasks.

### 1. **Input Understanding**

I'll intelligently parse your request:
- Natural language → Structured task
- Detect priority from keywords (urgent, ASAP, important)
- Infer dependencies from context
- Suggest complexity based on description
- Determine task type (feature, bug, refactor, test, docs)

### 2. **Smart Parsing Examples**

**"Add urgent task to fix login bug"**
→ Title: Fix login bug
→ Priority: high
→ Type: bug
→ Suggested complexity: medium

**"Create task for API documentation after task 23 is done"**
→ Title: API documentation
→ Dependencies: [23]
→ Type: documentation
→ Priority: medium

**"Need to refactor auth module - depends on 12 and 15, high complexity"**
→ Title: Refactor auth module
→ Dependencies: [12, 15]
→ Complexity: high
→ Type: refactor

### 3. **Context Enhancement**

Based on current project state:
- Suggest related existing tasks
- Warn about potential conflicts
- Recommend dependencies
- Propose subtasks if complex

### 4. **Interactive Refinement**

```yaml
Task Preview:
─────────────
Title: [Extracted title]
Priority: [Inferred priority]
Dependencies: [Detected dependencies]
Complexity: [Estimated complexity]

Suggestions:
- Similar task #34 exists, consider as dependency?
- This seems complex, break into subtasks?
- Tasks #45-47 work on same module
```

### 5. **Validation & Creation**

Before creating:
- Validate dependencies exist
- Check for duplicates
- Ensure logical ordering
- Verify task completeness

### 6. **Smart Defaults**

Intelligent defaults based on:
- Task type patterns
- Team conventions
- Historical data
- Current sprint/phase

Result: High-quality tasks from minimal input.

--- .cursor/commands/tm/analyze-complexity.md ---
Analyze task complexity and generate expansion recommendations.

Arguments: $ARGUMENTS

Perform deep analysis of task complexity across the project.

## Complexity Analysis

Uses AI to analyze tasks and recommend which ones need breakdown.

## Execution Options

```bash
task-master analyze-complexity [--research] [--threshold=5]
```

## Analysis Parameters

- `--research` → Use research AI for deeper analysis
- `--threshold=5` → Only flag tasks above complexity 5
- Default: Analyze all pending tasks

## Analysis Process

### 1. **Task Evaluation**
For each task, AI evaluates:
- Technical complexity
- Time requirements
- Dependency complexity
- Risk factors
- Knowledge requirements

### 2. **Complexity Scoring**
Assigns score 1-10 based on:
- Implementation difficulty
- Integration challenges
- Testing requirements
- Unknown factors
- Technical debt risk

### 3. **Recommendations**
For complex tasks:
- Suggest expansion approach
- Recommend subtask breakdown
- Identify risk areas
- Propose mitigation strategies

## Smart Analysis Features

1. **Pattern Recognition**
   - Similar task comparisons
   - Historical complexity accuracy
   - Team velocity consideration
   - Technology stack factors

2. **Contextual Factors**
   - Team expertise
   - Available resources
   - Timeline constraints
   - Business criticality

3. **Risk Assessment**
   - Technical risks
   - Timeline risks
   - Dependency risks
   - Knowledge gaps

## Output Format

```
Task Complexity Analysis Report
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

High Complexity Tasks (>7):
📍 #5 "Implement real-time sync" - Score: 9/10
   Factors: WebSocket complexity, state management, conflict resolution
   Recommendation: Expand into 5-7 subtasks
   Risks: Performance, data consistency

📍 #12 "Migrate database schema" - Score: 8/10
   Factors: Data migration, zero downtime, rollback strategy
   Recommendation: Expand into 4-5 subtasks
   Risks: Data loss, downtime

Medium Complexity Tasks (5-7):
📍 #23 "Add export functionality" - Score: 6/10
   Consider expansion if timeline tight

Low Complexity Tasks (<5):
✅ 15 tasks - No expansion needed

Summary:
- Expand immediately: 2 tasks
- Consider expanding: 5 tasks
- Keep as-is: 15 tasks
```

## Actionable Output

For each high-complexity task:
1. Complexity score with reasoning
2. Specific expansion suggestions
3. Risk mitigation approaches
4. Recommended subtask structure

## Integration

Results are:
- Saved to `.taskmaster/reports/complexity-analysis.md`
- Used by expand command
- Inform sprint planning
- Guide resource allocation

## Next Steps

After analysis:
```
/taskmaster:expand 5    # Expand specific task
/taskmaster:expand-all  # Expand all recommended
/taskmaster:complexity-report  # View detailed report
```

--- .cursor/commands/tm/analyze-project.md ---
Advanced project analysis with actionable insights and recommendations.

Arguments: $ARGUMENTS

## Comprehensive Project Analysis

Multi-dimensional analysis based on requested focus area.

### 1. **Analysis Modes**

Based on $ARGUMENTS:
- "velocity" → Sprint velocity and trends
- "quality" → Code quality metrics
- "risk" → Risk assessment and mitigation
- "dependencies" → Dependency graph analysis
- "team" → Workload and skill distribution
- "architecture" → System design coherence
- Default → Full spectrum analysis

### 2. **Velocity Analytics**

```
📊 Velocity Analysis
━━━━━━━━━━━━━━━━━━━
Current Sprint: 24 points/week ↗️ +20%
Rolling Average: 20 points/week
Efficiency: 85% (17/20 tasks on time)

Bottlenecks Detected:
- Code review delays (avg 4h wait)
- Test environment availability
- Dependency on external team

Recommendations:
1. Implement parallel review process
2. Add staging environment
3. Mock external dependencies
```

### 3. **Risk Assessment**

**Technical Risks**
- High complexity tasks without backup assignee
- Single points of failure in architecture
- Insufficient test coverage in critical paths
- Technical debt accumulation rate

**Project Risks**
- Critical path dependencies
- Resource availability gaps
- Deadline feasibility analysis
- Scope creep indicators

### 4. **Dependency Intelligence**

Visual dependency analysis:
```
Critical Path:
#12 → #15 → #23 → #45 → #50 (20 days)
         ↘ #24 → #46 ↗

Optimization: Parallelize #15 and #24
Time Saved: 3 days
```

### 5. **Quality Metrics**

**Code Quality**
- Test coverage trends
- Complexity scores
- Technical debt ratio
- Review feedback patterns

**Process Quality**
- Rework frequency
- Bug introduction rate
- Time to resolution
- Knowledge distribution

### 6. **Predictive Insights**

Based on patterns:
- Completion probability by deadline
- Resource needs projection
- Risk materialization likelihood
- Suggested interventions

### 7. **Executive Dashboard**

High-level summary with:
- Health score (0-100)
- Top 3 risks
- Top 3 opportunities
- Recommended actions
- Success probability

Result: Data-driven decisions with clear action paths.

--- .cursor/commands/tm/auto-implement-tasks.md ---
Enhanced auto-implementation with intelligent code generation and testing.

Arguments: $ARGUMENTS

## Intelligent Auto-Implementation

Advanced implementation with context awareness and quality checks.

### 1. **Pre-Implementation Analysis**

Before starting:
- Analyze task complexity and requirements
- Check codebase patterns and conventions
- Identify similar completed tasks
- Assess test coverage needs
- Detect potential risks

### 2. **Smart Implementation Strategy**

Based on task type and context:

**Feature Tasks**
1. Research existing patterns
2. Design component architecture
3. Implement with tests
4. Integrate with system
5. Update documentation

**Bug Fix Tasks**
1. Reproduce issue
2. Identify root cause
3. Implement minimal fix
4. Add regression tests
5. Verify side effects

**Refactoring Tasks**
1. Analyze current structure
2. Plan incremental changes
3. Maintain test coverage
4. Refactor step-by-step
5. Verify behavior unchanged

### 3. **Code Intelligence**

**Pattern Recognition**
- Learn from existing code
- Follow team conventions
- Use preferred libraries
- Match style guidelines

**Test-Driven Approach**
- Write tests first when possible
- Ensure comprehensive coverage
- Include edge cases
- Performance considerations

### 4. **Progressive Implementation**

Step-by-step with validation:
```
Step 1/5: Setting up component structure ✓
Step 2/5: Implementing core logic ✓
Step 3/5: Adding error handling ⚡ (in progress)
Step 4/5: Writing tests ⏳
Step 5/5: Integration testing ⏳

Current: Adding try-catch blocks and validation...
```

### 5. **Quality Assurance**

Automated checks:
- Linting and formatting
- Test execution
- Type checking
- Dependency validation
- Performance analysis

### 6. **Smart Recovery**

If issues arise:
- Diagnostic analysis
- Suggestion generation
- Fallback strategies
- Manual intervention points
- Learning from failures

### 7. **Post-Implementation**

After completion:
- Generate PR description
- Update documentation
- Log lessons learned
- Suggest follow-up tasks
- Update task relationships

Result: High-quality, production-ready implementations.

--- .cursor/commands/tm/command-pipeline.md ---
Execute a pipeline of commands based on a specification.

Arguments: $ARGUMENTS

## Command Pipeline Execution

Parse pipeline specification from arguments. Supported formats:

### Simple Pipeline
`init → expand-all → sprint-plan`

### Conditional Pipeline
`status → if:pending>10 → sprint-plan → else → next`

### Iterative Pipeline
`for:pending-tasks → expand → complexity-check`

### Smart Pipeline Patterns

**1. Project Setup Pipeline**
```
init [prd] →
expand-all →
complexity-report →
sprint-plan →
show first-sprint
```

**2. Daily Work Pipeline**
```
standup →
if:in-progress → continue →
else → next → start
```

**3. Task Completion Pipeline**
```
complete [id] →
git-commit →
if:blocked-tasks-freed → show-freed →
next
```

**4. Quality Check Pipeline**
```
list in-progress →
for:each → check-idle-time →
if:idle>1day → prompt-update
```

### Pipeline Features

**Variables**
- Store results: `status → $count=pending-count`
- Use in conditions: `if:$count>10`
- Pass between commands: `expand $high-priority-tasks`

**Error Handling**
- On failure: `try:complete → catch:show-blockers`
- Skip on error: `optional:test-run`
- Retry logic: `retry:3:commit`

**Parallel Execution**
- Parallel branches: `[analyze | test | lint]`
- Join results: `parallel → join:report`

### Execution Flow

1. Parse pipeline specification
2. Validate command sequence
3. Execute with state passing
4. Handle conditions and loops
5. Aggregate results
6. Show summary

This enables complex workflows like:
`parse-prd → expand-all → filter:complex>70 → assign:senior → sprint-plan:weighted`

--- .cursor/commands/tm/complexity-report.md ---
Display the task complexity analysis report.

Arguments: $ARGUMENTS

View the detailed complexity analysis generated by analyze-complexity command.

## Viewing Complexity Report

Shows comprehensive task complexity analysis with actionable insights.

## Execution

```bash
task-master complexity-report [--file=<path>]
```

## Report Location

Default: `.taskmaster/reports/complexity-analysis.md`
Custom: Specify with --file parameter

## Report Contents

### 1. **Executive Summary**
```
Complexity Analysis Summary
━━━━━━━━━━━━━━━━━━━━━━━━
Analysis Date: 2024-01-15
Tasks Analyzed: 32
High Complexity: 5 (16%)
Medium Complexity: 12 (37%)
Low Complexity: 15 (47%)

Critical Findings:
- 5 tasks need immediate expansion
- 3 tasks have high technical risk
- 2 tasks block critical path
```

### 2. **Detailed Task Analysis**
For each complex task:
- Complexity score breakdown
- Contributing factors
- Specific risks identified
- Expansion recommendations
- Similar completed tasks

### 3. **Risk Matrix**
Visual representation:
```
Risk vs Complexity Matrix
━━━━━━━━━━━━━━━━━━━━━━━
High Risk  | #5(9) #12(8) | #23(6)
Med Risk   | #34(7)       | #45(5) #67(5)
Low Risk   | #78(8)       | [15 tasks]
           | High Complex  | Med Complex
```

### 4. **Recommendations**

**Immediate Actions:**
1. Expand task #5 - Critical path + high complexity
2. Expand task #12 - High risk + dependencies
3. Review task #34 - Consider splitting

**Sprint Planning:**
- Don't schedule multiple high-complexity tasks together
- Ensure expertise available for complex tasks
- Build in buffer time for unknowns

## Interactive Features

When viewing report:
1. **Quick Actions**
   - Press 'e' to expand a task
   - Press 'd' for task details
   - Press 'r' to refresh analysis

2. **Filtering**
   - View by complexity level
   - Filter by risk factors
   - Show only actionable items

3. **Export Options**
   - Markdown format
   - CSV for spreadsheets
   - JSON for tools

## Report Intelligence

- Compares with historical data
- Shows complexity trends
- Identifies patterns
- Suggests process improvements

## Integration

Use report for:
- Sprint planning sessions
- Resource allocation
- Risk assessment
- Team discussions
- Client updates

## Example Usage

```
/taskmaster:complexity-report
→ Opens latest analysis

/taskmaster:complexity-report --file=archived/2024-01-01.md
→ View historical analysis

After viewing:
/taskmaster:expand 5
→ Expand high-complexity task
```

--- .cursor/commands/tm/convert-task-to-subtask.md ---
Convert an existing task into a subtask.

Arguments: $ARGUMENTS

Parse parent ID and task ID to convert.

## Task Conversion

Converts an existing standalone task into a subtask of another task.

## Argument Parsing

- "move task 8 under 5"
- "make 8 a subtask of 5"
- "nest 8 in 5"
- "5 8" → make task 8 a subtask of task 5

## Execution

```bash
task-master add-subtask --parent=<parent-id> --task-id=<task-to-convert>
```

## Pre-Conversion Checks

1. **Validation**
   - Both tasks exist and are valid
   - No circular parent relationships
   - Task isn't already a subtask
   - Logical hierarchy makes sense

2. **Impact Analysis**
   - Dependencies that will be affected
   - Tasks that depend on converting task
   - Priority alignment needed
   - Status compatibility

## Conversion Process

1. Change task ID from "8" to "5.1" (next available)
2. Update all dependency references
3. Inherit parent's context where appropriate
4. Adjust priorities if needed
5. Update time estimates

## Smart Features

- Preserve task history
- Maintain dependencies
- Update all references
- Create conversion log

## Example

```
/taskmaster:add-subtask/from-task 5 8
→ Converting: Task #8 becomes subtask #5.1
→ Updated: 3 dependency references
→ Parent task #5 now has 1 subtask
→ Note: Subtask inherits parent's priority

Before: #8 "Implement validation" (standalone)
After:  #5.1 "Implement validation" (subtask of #5)
```

## Post-Conversion

- Show new task hierarchy
- List updated dependencies
- Verify project integrity
- Suggest related conversions

--- .cursor/commands/tm/expand-all-tasks.md ---
Expand all pending tasks that need subtasks.

## Bulk Task Expansion

Intelligently expands all tasks that would benefit from breakdown.

## Execution

```bash
task-master expand --all
```

## Smart Selection

Only expands tasks that:
- Are marked as pending
- Have high complexity (>5)
- Lack existing subtasks
- Would benefit from breakdown

## Expansion Process

1. **Analysis Phase**
   - Identify expansion candidates
   - Group related tasks
   - Plan expansion strategy

2. **Batch Processing**
   - Expand tasks in logical order
   - Maintain consistency
   - Preserve relationships
   - Optimize for parallelism

3. **Quality Control**
   - Ensure subtask quality
   - Avoid over-decomposition
   - Maintain task coherence
   - Update dependencies

## Options

- Add `force` to expand all regardless of complexity
- Add `research` for enhanced AI analysis

## Results

After bulk expansion:
- Summary of tasks expanded
- New subtask count
- Updated complexity metrics
- Suggested task order

--- src/__init__.py ---


--- src/artifact_ingest/index.py ---
import json
from pathlib import Path
from typing import List, Dict, Optional

def generate_registry_index(docs_root: Path, output_path: Path = None):
    """
    Scan docs_root for sources and generate an index.json.
    """
    index = []
    
    # Iterate over directories in docs_root
    for source_dir in docs_root.iterdir():
        if not source_dir.is_dir():
            continue
            
        metadata_path = source_dir / "metadata.json"
        source_data = {
            "id": source_dir.name,
            "artifacts": []
        }
        
        # Try to load metadata if it exists
        if metadata_path.exists():
            try:
                meta = json.loads(metadata_path.read_text(encoding="utf-8"))
                source_data["last_refreshed"] = meta.get("last_refreshed")
                source_data["model_used"] = meta.get("model_used")
                source_data["description"] = meta.get("description") # If we added this to metadata
            except Exception:
                pass
        
        # Scan for artifacts directly to be sure
        # Match llms.txt, project-llms.txt, project-llms-ctx.txt, etc.
        patterns = ["llms.txt", "*-llms*.txt"]
        found_artifacts = set()
        
        for pattern in patterns:
            for artifact in source_dir.glob(pattern):
                found_artifacts.add(str(artifact.relative_to(docs_root)))
        
        source_data["artifacts"] = sorted(list(found_artifacts))
            
        if source_data["artifacts"]:
            index.append(source_data)
            
    if output_path is None:
        output_path = docs_root / "index.json"
        
    with output_path.open("w", encoding="utf-8") as f:
        json.dump(index, f, indent=2)
        f.write("\n")
        
    return index

--- src/artifact_ingest/ingest.py ---
import shutil
import json
import time
from pathlib import Path
from typing import List, Dict, Optional

def normalize_line_endings(path: Path):
    """Ensure file uses LF line endings."""
    content = path.read_bytes()
    normalized = content.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
    path.write_bytes(normalized)

def ingest_artifacts(
    source_id: str, 
    temp_dir: Path, 
    docs_root: Path, 
    model_used: Optional[str] = None
) -> List[str]:
    """Normalize and move artifacts to docs/<id>/, and generate metadata."""
    target_dir = docs_root / source_id
    target_dir.mkdir(parents=True, exist_ok=True)
    
    ingested_files = []
    
    # Find all *-llms*.txt files in the temp dir
    for artifact in temp_dir.glob("**/*-llms*.txt"):
        # Normalize and copy
        normalize_line_endings(artifact)
        
        target_path = target_dir / artifact.name
        shutil.copy2(artifact, target_path)
        ingested_files.append(str(target_path.relative_to(docs_root)))

    # Generate metadata.json
    metadata = {
        "source_id": source_id,
        "last_refreshed": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
        "model_used": model_used or "unknown",
        "artifacts": ingested_files
    }
    
    with (target_dir / "metadata.json").open("w", encoding="utf-8") as f:
        json.dump(metadata, f, indent=2)
        f.write("\n")
        
    return ingested_files


--- src/artifact_ingest/__init__.py ---


--- src/generator_runner/__init__.py ---


--- src/git_sync/__init__.py ---


--- src/registry_config/__init__.py ---


--- src/reporting/__init__.py ---


--- src/registry_config/loader.py ---
import json
from pathlib import Path
from typing import Optional
from .models import Manifest

def load_manifest(path: str = "sources.json") -> Manifest:
    """Load and validate the source manifest."""
    p = Path(path)
    if not p.exists():
        return Manifest()
    
    with p.open("r", encoding="utf-8") as f:
        data = json.load(f)
    
    return Manifest.model_validate(data)

def save_manifest(manifest: Manifest, path: str = "sources.json") -> None:
    """Save the manifest to disk with consistent formatting."""
    with Path(path).open("w", encoding="utf-8") as f:
        json.dump(manifest.model_dump(exclude_none=True), f, indent=2)
        f.write("\n")


--- src/registry_config/models.py ---
from __future__ import annotations
from typing import List, Optional, Dict, Any
from pydantic import BaseModel, Field, field_validator, AnyHttpUrl
import re

SLUG_REGEX = re.compile(r'^[a-z0-9]+(?:-[a-z0-9]+)*$')

class GeneratorProfile(BaseModel):
    name: str = Field(..., description="Unique name of the profile")
    description: Optional[str] = None
    timeout: int = Field(default=300, description="Execution timeout in seconds")
    headers: Dict[str, str] = Field(default_factory=dict, description="Custom headers for the request")
    model: Optional[str] = Field(default=None, description="Preferred model identifier")

class Source(BaseModel):
    id: str = Field(..., description="Unique URL-safe slug ID for the source")
    url: AnyHttpUrl = Field(..., description="URL of the repository or documentation site")
    type: str = Field(default="repo", description="Type of source: 'repo' or 'site'")
    profile: Optional[str] = Field(default=None, description="Name of the generator profile to use")
    enabled: bool = Field(default=True, description="Whether this source should be processed")
    tags: List[str] = Field(default_factory=list, description="Categorization tags")
    
    # Metadata fields (updated during refresh)
    last_refreshed: Optional[str] = Field(default=None, description="ISO 8601 timestamp of last successful refresh")
    last_model_used: Optional[str] = Field(default=None, description="Model used for the last generation")

    @field_validator('id')
    @classmethod
    def validate_slug(cls, v: str) -> str:
        if not SLUG_REGEX.match(v):
            raise ValueError(f"ID '{v}' must be a URL-safe slug (lowercase alphanumeric with hyphens)")
        return v

class Manifest(BaseModel):
    version: str = Field(default="1.0", description="Manifest schema version")
    profiles: Dict[str, GeneratorProfile] = Field(default_factory=dict, description="Shared generator profiles")
    sources: List[Source] = Field(default_factory=list, description="List of documentation sources")

    @field_validator('sources')
    @classmethod
    def validate_unique_ids(cls, v: List[Source]) -> List[Source]:
        ids = [s.id for s in v]
        if len(ids) != len(set(ids)):
            from collections import Counter
            duplicates = [item for item, count in Counter(ids).items() if count > 1]
            raise ValueError(f"Duplicate source IDs found: {duplicates}")
        return v


--- tests/test_index.py ---
import json
from pathlib import Path
from src.artifact_ingest.index import generate_registry_index

def test_generate_index(tmp_path):
    docs_root = tmp_path / "docs"
    docs_root.mkdir()
    
    # Source 1 with artifacts and metadata
    src1 = docs_root / "src1"
    src1.mkdir()
    (src1 / "llms.txt").touch()
    (src1 / "metadata.json").write_text(json.dumps({"model_used": "gpt-4"}))
    
    # Source 2 with artifacts only
    src2 = docs_root / "src2"
    src2.mkdir()
    (src2 / "foo-llms.txt").touch()
    
    # Source 3 empty
    (docs_root / "src3").mkdir()
    
    index = generate_registry_index(docs_root)
    
    assert len(index) == 2
    
    s1 = next(s for s in index if s["id"] == "src1")
    assert s1["model_used"] == "gpt-4"
    assert "src1/llms.txt" in s1["artifacts"]
    
    s2 = next(s for s in index if s["id"] == "src2")
    assert "src2/foo-llms.txt" in s2["artifacts"]
    assert "model_used" not in s2
    
    assert (docs_root / "index.json").exists()


--- tests/test_ingest.py ---
import json
from pathlib import Path
from src.artifact_ingest.ingest import ingest_artifacts

def test_ingest_artifacts(tmp_path):
    # Setup source dir
    temp_dir = tmp_path / "temp"
    temp_dir.mkdir()
    artifact = temp_dir / "foo-llms.txt"
    artifact.write_bytes(b"Hello\r\nWorld") # CRLF
    
    docs_root = tmp_path / "docs"
    docs_root.mkdir()
    
    ingested = ingest_artifacts("test-src", temp_dir, docs_root, model_used="gpt-4")
    
    # Check location
    target_file = docs_root / "test-src" / "foo-llms.txt"
    assert target_file.exists()
    assert str(target_file.relative_to(docs_root)) in ingested
    
    # Check normalization
    assert target_file.read_bytes() == b"Hello\nWorld"
    
    # Check metadata
    meta_file = docs_root / "test-src" / "metadata.json"
    assert meta_file.exists()
    meta = json.loads(meta_file.read_text())
    assert meta["source_id"] == "test-src"
    assert meta["model_used"] == "gpt-4"
    assert len(meta["artifacts"]) == 1


--- tests/test_registry_config.py ---
import pytest
from pathlib import Path
import json
from src.registry_config.models import Manifest, Source, GeneratorProfile
from src.registry_config.loader import load_manifest
from pydantic import ValidationError

def test_source_validation_slug():
    # Valid slug
    s = Source(id="valid-slug", url="https://example.com")
    assert s.id == "valid-slug"

    # Invalid slug (uppercase)
    with pytest.raises(ValidationError):
        Source(id="Invalid-Slug", url="https://example.com")

    # Invalid slug (spaces)
    with pytest.raises(ValidationError):
        Source(id="invalid slug", url="https://example.com")

def test_manifest_duplicate_ids():
    sources = [
        Source(id="s1", url="https://a.com"),
        Source(id="s1", url="https://b.com")
    ]
    with pytest.raises(ValidationError) as exc:
        Manifest(sources=sources)
    assert "Duplicate source IDs" in str(exc.value)

def test_load_manifest(tmp_path):
    manifest_file = tmp_path / "sources.json"
    data = {
        "version": "1.0",
        "profiles": {
            "default": {"name": "default", "timeout": 60}
        },
        "sources": [
            {"id": "test-source", "url": "https://example.com", "profile": "default"}
        ]
    }
    manifest_file.write_text(json.dumps(data))
    
    manifest = load_manifest(str(manifest_file))
    assert len(manifest.sources) == 1
    assert manifest.sources[0].id == "test-source"
    assert manifest.profiles["default"].timeout == 60

def test_load_missing_manifest():
    manifest = load_manifest("non_existent.json")
    assert isinstance(manifest, Manifest)
    assert len(manifest.sources) == 0


--- tests/test_reporting.py ---
import json
from pathlib import Path
from src.reporting.report import RunReport, SourceResult

def test_report_lifecycle():
    report = RunReport(run_id="test-run")
    assert report.start_time > 0
    assert report.end_time is None
    
    result = SourceResult(id="s1", status="success", duration=1.5)
    report.record_result(result)
    
    assert report.summary["success"] == 1
    assert report.results["s1"] == result
    
    report.finalize()
    assert report.end_time >= report.start_time

def test_atomic_write(tmp_path):
    report_file = tmp_path / "report.json"
    report = RunReport(run_id="test-run")
    report.record_result(SourceResult(id="s1", status="success", duration=1.0))
    report.finalize()
    
    report.to_json(str(report_file))
    
    assert report_file.exists()
    content = json.loads(report_file.read_text())
    assert content["run_id"] == "test-run"
    assert content["summary"]["success"] == 1


--- tests/test_runner.py ---
import subprocess
import pytest
from unittest.mock import patch, MagicMock
from pathlib import Path
from src.generator_runner.runner import GeneratorRunner
from src.registry_config.models import Source

@patch("subprocess.run")
def test_runner_success(mock_run, tmp_path):
    runner = GeneratorRunner(output_root=tmp_path)
    source = Source(id="test-src", url="https://github.com/test/repo")
    
    # Mock successful result
    mock_result = MagicMock()
    mock_result.returncode = 0
    mock_result.stdout = "Success"
    mock_run.return_value = mock_result
    
    # Create fake artifact
    (tmp_path / "temp_work" / "test-src").mkdir(parents=True)
    (tmp_path / "temp_work" / "test-src" / "test-llms.txt").touch()
    
    result = runner.run_source(source)
    
    assert result["status"] == "success"
    assert "test-llms.txt" in result["artifacts"][0]
    
    # Verify command structure
    args, kwargs = mock_run.call_args
    cmd = args[0]
    assert cmd[0] == "lmstudio-lmstxt"
    assert cmd[1] == "https://github.com/test/repo"
    assert "--output-dir" in cmd
    assert "--stamp" in cmd

@patch("subprocess.run")
def test_runner_failure(mock_run, tmp_path):
    runner = GeneratorRunner(output_root=tmp_path)
    source = Source(id="test-src", url="https://github.com/test/repo")
    
    mock_result = MagicMock()
    mock_result.returncode = 1
    mock_result.stderr = "Error"
    mock_run.return_value = mock_result
    
    result = runner.run_source(source)
    assert result["status"] == "failure"
    assert result["error"] == "Error"

@patch("subprocess.run")
def test_runner_timeout(mock_run, tmp_path):
    runner = GeneratorRunner(output_root=tmp_path)
    source = Source(id="test-src", url="https://github.com/test/repo")
    
    mock_run.side_effect = subprocess.TimeoutExpired(cmd="cmd", timeout=300)
    
    result = runner.run_source(source)
    assert result["status"] == "failure"
    assert "timed out" in result["error"]
